diff --git a/drivers/usb/typec/mmi_tcpc/Android.mk b/drivers/usb/typec/mmi_tcpc/Android.mk new file mode 100644 index 000000000000..5f1b62aa469a --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/Android.mk @@ -0,0 +1,34 @@ +DLKM_DIR := motorola/kernel/modules +LOCAL_PATH := $(call my-dir) + +#ifeq ($(TCPC_RT1711H),true) + KERNEL_CFLAGS += CONFIG_TCPC_RT1711H=y +#endif + +#ifeq ($(TCPC_CLASS),true) + KERNEL_CFLAGS += CONFIG_TCPC_CLASS=y +#endif + +#ifeq ($(USB_POWER_DELIVERY),true) + KERNEL_CFLAGS += CONFIG_USB_POWER_DELIVERY=y +#endif + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := tcpc_rt1711h.ko +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk + + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := tcpc_class.ko +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk + + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := rt_pd_manager.ko +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk diff --git a/drivers/usb/typec/mmi_tcpc/Kbuild b/drivers/usb/typec/mmi_tcpc/Kbuild new file mode 100644 index 000000000000..25f2bf82a740 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/Kbuild @@ -0,0 +1,37 @@ +# add -Wall to try to catch everything we can. +EXTRA_CFLAGS += -Wall +EXTRA_CFLAGS += -I$(ANDROID_BUILD_TOP)/motorola/kernel/modules/include \ + -I$(ANDROID_BUILD_TOP)/motorola/kernel/modules/drivers/usb/typec/mmi_tcpc + +subdir-ccflags-y += -Wall -Werror -DCONFIG_RT_REGMAP + +#ifneq ($(filter m y,$(CONFIG_TCPC_CLASS)),) + EXTRA_CFLAGS += -DCONFIG_TCPC_CLASS +#endif + +#ifneq ($(filter m y,$(CONFIG_USB_POWER_DELIVERY)),) + EXTRA_CFLAGS += -DCONFIG_USB_POWER_DELIVERY +#endif + +#ifneq ($(filter m y,$(CONFIG_TCPC_RT1711H)),) + obj-m += tcpc_rt1711h.o +#endif + +tcpc_class-objs += tcpci_core.o tcpci_typec.o tcpci_timer.o \ + tcpm.o tcpci.o pd_dbg_info.o tcpci_alert.o rt-regmap.o tcpci_dual_role.o + +#ifneq ($(filter m y,$(CONFIG_USB_POWER_DELIVERY)),) +tcpc_class-objs += tcpci_event.o \ + pd_core.o pd_policy_engine.o pd_process_evt.o \ + pd_dpm_core.o pd_dpm_uvdm.o pd_dpm_alt_mode_dp.o pd_dpm_pdo_select.o\ + pd_dpm_reaction.o \ + pd_process_evt_snk.o pd_process_evt_src.o pd_process_evt_vdm.o \ + pd_process_evt_drs.o pd_process_evt_prs.o pd_process_evt_vcs.o \ + pd_process_evt_dbg.o pd_process_evt_tcp.o pd_process_evt_com.o \ + pd_policy_engine_src.o pd_policy_engine_snk.o pd_policy_engine_ufp.o pd_policy_engine_vcs.o \ + pd_policy_engine_dfp.o pd_policy_engine_dr.o pd_policy_engine_drs.o pd_policy_engine_prs.o \ + pd_policy_engine_dbg.o pd_policy_engine_com.o pd_dpm_alt_mode_dc.o +#endif + +obj-m += tcpc_class.o +obj-m += rt_pd_manager.o diff --git a/drivers/usb/typec/mmi_tcpc/Makefile b/drivers/usb/typec/mmi_tcpc/Makefile new file mode 100644 index 000000000000..62a13fd28500 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/Makefile @@ -0,0 +1,11 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean + diff --git a/drivers/usb/typec/mmi_tcpc/inc/pd_core.h b/drivers/usb/typec/mmi_tcpc/inc/pd_core.h new file mode 100644 index 000000000000..f639d8b62df6 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/pd_core.h @@ -0,0 +1,1634 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PD_CORE_H_ +#define PD_CORE_H_ + +#include +#include +#include "tcpci_timer.h" +#include "tcpci_event.h" +#include "pd_dbg_info.h" +#include "tcpm.h" + +#define PD_BUG_ON(x) WARN_ON(x) +/*---------------------------------------------------------------------------*/ + +#ifndef CONFIG_TCPC_SOURCE_VCONN +#undef CONFIG_USB_PD_VCONN_SWAP +#undef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#undef CONFIG_USB_PD_DFP_READY_DISCOVER_ID +#undef CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN +#undef CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN +#undef CONFIG_USB_PD_ALT_MODE_SVID +#undef CONFIG_USB_PD_RESET_CABLE +#endif /* CONFIG_TCPC_SOURCE_VCONN */ + +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#define CONFIG_PD_DISCOVER_CABLE_ID +#endif /* CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID +#undef CONFIG_PD_DISCOVER_CABLE_ID +#define CONFIG_PD_DISCOVER_CABLE_ID +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_RESET_CABLE + +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#define CONFIG_PD_SRC_RESET_CABLE +#endif /* CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID +#define CONFIG_PD_DFP_RESET_CABLE +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ + +#endif /* CONFIG_USB_PD_RESET_CABLE */ + +/*---------------------------------------------------------------------------*/ + +#define PD_SOP_NR 3 + +/* Default retry count for transmitting */ +/* Set to 0 for TD.PD.LL.E2 Retransmission */ +#define PD_RETRY_COUNT 3 +#define PD30_RETRY_COUNT 2 + +#if PD_RETRY_COUNT > 3 +#error "PD_RETRY_COUNT Max = 3" +#endif + +/* PDO : Power Data Object */ +/* + * 1. The vSafe5V Fixed Supply Object shall always be the first object. + * 2. The remaining Fixed Supply Objects, + * if present, shall be sent in voltage order; lowest to highest. + * 3. The Battery Supply Objects, + * if present shall be sent in Minimum Voltage order; lowest to highest. + * 4. The Variable Supply (non battery) Objects, + * if present, shall be sent in Minimum Voltage order; lowest to highest. + */ +#define PDO_TYPE_FIXED (0 << 30) +#define PDO_TYPE_BATTERY (1 << 30) +#define PDO_TYPE_VARIABLE (2 << 30) +#define PDO_TYPE_APDO (3 << 30) +#define PDO_TYPE_MASK (3 << 30) + +#define PDO_FIXED_DUAL_ROLE (1 << 29) /* Dual role device */ +#define PDO_FIXED_SUSPEND (1 << 28) /* USB Suspend supported (SRC) */ +#define PDO_FIXED_HIGH_CAP (1 << 28) /* Higher Capability (SNK) */ +#define PDO_FIXED_EXTERNAL (1 << 27) /* Externally powered */ +#define PDO_FIXED_COMM_CAP (1 << 26) /* USB Communications Capable */ +#define PDO_FIXED_DATA_SWAP (1 << 25) /* Data role swap command supported */ + +#define PDO_FIXED_PEAK_CURR(i) \ + ((i & 0x03) << 20) /* [21..20] Peak current */ +#define PDO_FIXED_VOLT(mv) \ + ((((mv)/50) & 0x3ff) << 10) /* Voltage in 50mV units */ +#define PDO_FIXED_CURR(ma) \ + ((((ma)/10) & 0x3ff) << 0) /* Max current in 10mA units */ + +#define PDO_TYPE(raw) (raw & PDO_TYPE_MASK) +#define PDO_TYPE_VAL(raw) (PDO_TYPE(raw) >> 30) + +#define PDO_FIXED_EXTRACT_VOLT_RAW(raw) (((raw) >> 10) & 0x3ff) +#define PDO_FIXED_EXTRACT_CURR_RAW(raw) (((raw) >> 0) & 0x3ff) +#define PDO_FIXED_EXTRACT_VOLT(raw) (PDO_FIXED_EXTRACT_VOLT_RAW(raw) * 50) +#define PDO_FIXED_EXTRACT_CURR(raw) (PDO_FIXED_EXTRACT_CURR_RAW(raw) * 10) +#define PDO_FIXED_RESET_CURR(raw, ma) \ + ((raw & ~0x3ff) | PDO_FIXED_CURR(ma)) + +#define PDO_FIXED(mv, ma, flags) (PDO_FIXED_VOLT(mv) |\ + PDO_FIXED_CURR(ma) | (flags)) + +#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & 0x3FF) << 20) +#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & 0x3FF) << 10) +#define PDO_VAR_OP_CURR(ma) ((((ma) / 10) & 0x3FF) << 0) + +#define PDO_VAR_EXTRACT_MAX_VOLT_RAW(raw) (((raw) >> 20) & 0x3ff) +#define PDO_VAR_EXTRACT_MIN_VOLT_RAW(raw) (((raw) >> 10) & 0x3ff) +#define PDO_VAR_EXTRACT_CURR_RAW(raw) (((raw) >> 0) & 0x3ff) + +#define PDO_VAR_EXTRACT_MAX_VOLT(raw) (PDO_VAR_EXTRACT_MAX_VOLT_RAW(raw) * 50) +#define PDO_VAR_EXTRACT_MIN_VOLT(raw) (PDO_VAR_EXTRACT_MIN_VOLT_RAW(raw) * 50) +#define PDO_VAR_EXTRACT_CURR(raw) (PDO_VAR_EXTRACT_CURR_RAW(raw) * 10) + +#define PDO_VAR_RESET_CURR(raw, ma) \ + ((raw & ~0x3ff) | PDO_VAR_OP_CURR(ma)) + +#define PDO_VAR(min_mv, max_mv, op_ma) \ + (PDO_VAR_MIN_VOLT(min_mv) | \ + PDO_VAR_MAX_VOLT(max_mv) | \ + PDO_VAR_OP_CURR(op_ma) | \ + PDO_TYPE_VARIABLE) + +#define PDO_BATT_MAX_VOLT(mv) ((((mv) / 50) & 0x3FF) << 20) +#define PDO_BATT_MIN_VOLT(mv) ((((mv) / 50) & 0x3FF) << 10) +#define PDO_BATT_OP_POWER(mw) ((((mw) / 250) & 0x3FF) << 0) + +#define PDO_BATT_EXTRACT_MAX_VOLT_RAW(raw) (((raw) >> 20) & 0x3ff) +#define PDO_BATT_EXTRACT_MIN_VOLT_RAW(raw) (((raw) >> 10) & 0x3ff) +#define PDO_BATT_EXTRACT_OP_POWER_RAW(raw) (((raw) >> 0) & 0x3ff) + +#define PDO_BATT_EXTRACT_MAX_VOLT(raw) \ + (PDO_BATT_EXTRACT_MAX_VOLT_RAW(raw) * 50) +#define PDO_BATT_EXTRACT_MIN_VOLT(raw) \ + (PDO_BATT_EXTRACT_MIN_VOLT_RAW(raw) * 50) +#define PDO_BATT_EXTRACT_OP_POWER(raw) \ + (PDO_BATT_EXTRACT_OP_POWER_RAW(raw) * 250) + +#define PDO_BATT(min_mv, max_mv, op_mw) \ + (PDO_BATT_MIN_VOLT(min_mv) | \ + PDO_BATT_MAX_VOLT(max_mv) | \ + PDO_BATT_OP_POWER(op_mw) | \ + PDO_TYPE_BATTERY) + +/* APDO : Augmented Power Data Object */ + +#define APDO_TYPE_MASK (3 << 28) +#define APDO_TYPE_PPS (0 << 28) + +#define APDO_TYPE(raw) (raw & APDO_TYPE_MASK) +#define APDO_TYPE_VAL(raw) (APDO_TYPE(raw) >> 28) + +#define APDO_PPS_CURR_FOLDBACK (1<<26) +#define APDO_PPS_MAX_VOLT(mv) ((((mv) / 100) & 0xff) << 17) +#define APDO_PPS_MIN_VOLT(mv) ((((mv) / 100) & 0xff) << 8) +#define APDO_PPS_CURR(ma) ((((ma) / 50) & 0x7f) << 0) + +#define APDO_PPS_EXTRACT_MAX_VOLT_RAW(raw) (((raw) >> 17) & 0xff) +#define APDO_PPS_EXTRACT_MIN_VOLT_RAW(raw) (((raw) >> 8) & 0Xff) +#define APDO_PPS_EXTRACT_CURR_RAW(raw) (((raw) >> 0) & 0x7f) +#define APDO_PPS_EXTRACT_PWR_LIMIT(raw) ((raw >> 27) & 0x1) + +#define APDO_PPS_EXTRACT_MAX_VOLT(raw) \ + (APDO_PPS_EXTRACT_MAX_VOLT_RAW(raw) * 100) +#define APDO_PPS_EXTRACT_MIN_VOLT(raw) \ + (APDO_PPS_EXTRACT_MIN_VOLT_RAW(raw) * 100) +#define APDO_PPS_EXTRACT_CURR(raw) \ + (APDO_PPS_EXTRACT_CURR_RAW(raw) * 50) + +#define APDO_PPS(min_mv, max_mv, ma, flags) \ + (APDO_PPS_MIN_VOLT(min_mv) | \ + APDO_PPS_MAX_VOLT(max_mv) | \ + APDO_PPS_CURR(ma) | \ + flags | PDO_TYPE_APDO | APDO_TYPE_PPS) + +/* RDO : Request Data Object */ +#define RDO_OBJ_POS(n) (((n) & 0x7) << 28) +#define RDO_POS(rdo) (((rdo) >> 28) & 0x7) +#define RDO_GIVE_BACK (1 << 27) +#define RDO_CAP_MISMATCH (1 << 26) +#define RDO_COMM_CAP (1 << 25) +#define RDO_NO_SUSPEND (1 << 24) +#define RDO_EXTEND_MESSAGE (1 << 23) +#define RDO_CURR_FOLDBACK (1 << 22) + +#define RDO_FIXED_VAR_OP_CURR(ma) ((((ma) / 10) & 0x3FF) << 10) +#define RDO_FIXED_VAR_MAX_CURR(ma) ((((ma) / 10) & 0x3FF) << 0) + +#define RDO_FIXED_VAR_EXTRACT_OP_CURR(raw) (((raw >> 10 & 0x3ff)) * 10) +#define RDO_FIXED_VAR_EXTRACT_MAX_CURR(raw) (((raw >> 0 & 0x3ff)) * 10) + +#define RDO_BATT_OP_POWER(mw) ((((mw) / 250) & 0x3FF) << 10) +#define RDO_BATT_MAX_POWER(mw) ((((mw) / 250) & 0x3FF) << 0) + +#define RDO_BATT_EXTRACT_OP_POWER(raw) (((raw >> 10 & 0x3ff)) * 250) +#define RDO_BATT_EXTRACT_MAX_POWER(raw) (((raw >> 0 & 0x3ff)) * 250) + +#define RDO_APDO_OP_MV(mv) ((((mv) / 20) & 0x7FF) << 9) +#define RDO_APDO_OP_MA(ma) ((((ma) / 50) & 0x7F) << 0) + +#define RDO_APDO_EXTRACT_OP_MV(raw) (((raw >> 9 & 0x7FF)) * 20) +#define RDO_APDO_EXTRACT_OP_MA(raw) (((raw >> 0 & 0x7F)) * 50) + +#define RDO_FIXED(n, op_ma, max_ma, flags) \ + (RDO_OBJ_POS(n) | (flags) | \ + RDO_FIXED_VAR_OP_CURR(op_ma) | \ + RDO_FIXED_VAR_MAX_CURR(max_ma)) + +#define RDO_BATT(n, op_mw, max_mw, flags) \ + (RDO_OBJ_POS(n) | (flags) | \ + RDO_BATT_OP_POWER(op_mw) | \ + RDO_BATT_MAX_POWER(max_mw)) + +#define RDO_APDO(n, op_mv, op_ma, flags) \ + (RDO_OBJ_POS(n) | (flags) | \ + RDO_APDO_OP_MV(op_mv) | \ + RDO_APDO_OP_MA(op_ma)) + +/* BDO : BIST Data Object */ +#define BDO_MODE_RECV (0 << 28) +#define BDO_MODE_TRANSMIT (1 << 28) +#define BDO_MODE_COUNTERS (2 << 28) +#define BDO_MODE_CARRIER0 (3 << 28) +#define BDO_MODE_CARRIER1 (4 << 28) +#define BDO_MODE_CARRIER2 (5 << 28) +#define BDO_MODE_CARRIER3 (6 << 28) +#define BDO_MODE_EYE (7 << 28) +#define BDO_MODE_TEST_DATA (8 << 28) + +#define BDO_MODE(obj) (obj & (0xf << 28)) +#define BDO(mode, cnt) ((mode) | ((cnt) & 0xFFFF)) + +#define SVID_DISCOVERY_MAX 16 + +/* Protocol revision */ +#define PD_REV10 0 +#define PD_REV20 1 +#define PD_REV30 2 + +#define MAX_EXTENDED_MSG_CHUNK_LEN 26 +#define MAX_EXTENDED_MSG_LEGACY_LEN 26 + +#define PD_MSG_ID_MAX 8 + +/* build message header */ + +#define PD_HEADER_ROLE(prole, drole) \ + (((drole) << 5) | ((prole) << 8)) + +#define PD_HEADER_COMMON(msg_type, rev, id, cnt, ext, private) \ + ((msg_type) | (rev << 6) | \ + ((id) << 9) | ((cnt) << 12) | ((ext) << 15) | (private)) + +#define PD_HEADER_SOP(msg_type, rev, prole, drole, id, cnt, ext) \ + ((msg_type) | (rev << 6) | \ + ((drole) << 5) | ((prole) << 8) | \ + ((id) << 9) | ((cnt) << 12) | ((ext) << 15)) + +#define PD_HEADER_SOP_PRIME(msg_type, rev, cable_plug, id, cnt, ext) \ + ((msg_type) | (rev << 6) | \ + ((cable_plug) << 8) | \ + ((id) << 9) | ((cnt) << 12) | ((ext) << 15)) + +#define PD_HEADER_EXT(header) (((header) >> 15) & 1) /* pd30 */ +#define PD_HEADER_REV(header) (((header) >> 6) & 3) +#define PD_HEADER_CNT(header) (((header) >> 12) & 7) +#define PD_HEADER_TYPE(header) ((header) & 0x1F) +#define PD_HEADER_ID(header) (((header) >> 9) & 7) +#define PD_HEADER_PR(header) (((header) >> 8) & 1) +#define PD_HEADER_DR(header) (((header) >> 5) & 1) + +#define PD_EXT_HEADER_PAYLOAD_INDEX 2 + +#define PD_EXT_HEADER_CHUNKED(header) (((header) >> 15) & 1) +#define PD_EXT_HEADER_CHUNK_NR(header) (((header) >> 11) & 0xF) +#define PD_EXT_HEADER_REQUEST(header) (((header) >> 10) & 1) +#define PD_EXT_HEADER_DATA_SIZE(header) (((header) >> 0) & 0x1FF) + +#define PD_EXT_HEADER_CK(data_size, req, chunk_nr, chunked) \ + ((data_size) | (req << 10) | \ + ((chunk_nr) << 11) | (chunked << 15)) + +/* + * VDO : Vendor Defined Message Object + * VDM object is minimum of VDM header + 6 additional data objects. + */ + +/* + * VDM header + * ---------- + * <31:16> :: SVID + * <15> :: VDM type ( 1b == structured, 0b == unstructured ) + * <14:13> :: Structured VDM version (can only be 00 == 1.0 currently) + * <12:11> :: reserved + * <10:8> :: object position (1-7 valid ... used for enter/exit mode only) + * <7:6> :: command type (SVDM only?) + * <5> :: reserved (SVDM), command type (UVDM) + * <4:0> :: command + */ + +#define VDO(vid, type, custom) \ + (((vid) << 16) | \ + ((type) << 15) | \ + ((custom) & 0x7FFF)) + +#define VDO_S(svid, ver, cmd_type, cmd, obj) \ + VDO(svid, 1, VDO_SVDM_VERS(ver) | \ + VDO_CMDT(cmd_type) | VDO_OPOS(obj) | cmd) + +#define VDO_REPLY(ver, cmd_type, request_vdo) \ + (VDO_SVDM_VERS(ver) | VDO_CMDT(cmd_type) \ + | ((request_vdo) & (~0x60C0))) + +#define SVDM_REV10 0 +#define SVDM_REV20 1 + +#define VDO_SVDM_TYPE (1 << 15) +#define VDO_SVDM_VERS(x) (x << 13) +#define VDO_OPOS(x) (x << 8) +#define VDO_CMDT(x) (x << 6) + +#define CMDT_INIT 0 +#define CMDT_RSP_ACK 1 +#define CMDT_RSP_NAK 2 +#define CMDT_RSP_BUSY 3 + +#define CMD_DISCOVER_IDENT 1 +#define CMD_DISCOVER_SVID 2 +#define CMD_DISCOVER_MODES 3 +#define CMD_ENTER_MODE 4 +#define CMD_EXIT_MODE 5 +#define CMD_ATTENTION 6 +#define CMD_DP_STATUS 16 +#define CMD_DP_CONFIG 17 + +#define PD_VDO_VID(vdo) ((vdo) >> 16) +#define PD_VDO_SVDM(vdo) (((vdo) >> 15) & 1) +#define PD_VDO_OPOS(vdo) (((vdo) >> 8) & 0x7) +#define PD_VDO_CMD(vdo) ((vdo) & 0x1f) +#define PD_VDO_CMDT(vdo) (((vdo) >> 6) & 0x3) + +/* + * SVDM Identity request -> response + * + * Request is simply properly formatted SVDM header + * + * Response is 4 data objects: + * [0] :: SVDM header + * [1] :: Identitiy header + * [2] :: Cert Stat VDO + * [3] :: Product VDO + * [4] :: Cable / AMA VDO + * + */ + +#define VDO_INDEX_HDR 0 +#define VDO_INDEX_IDH 1 +#define VDO_INDEX_CSTAT 2 +#define VDO_INDEX_PRODUCT 3 +#define VDO_INDEX_CABLE 4 +#define VDO_INDEX_AMA 4 +#define VDO_I(name) VDO_INDEX_##name + +/* + * SVDM Identity Header + * -------------------- + * <31> :: data capable as a USB host + * <30> :: data capable as a USB device + * <29:27> :: product type for UFP/Cable + * <26> :: modal operation supported (1b == yes) + * <25:23> :: product type for DFP + * <22:16> :: SBZ + * <15:0> :: USB-IF assigned VID for this cable vendor + */ + +#define IDH_PTYPE_UNDEF 0 +#define IDH_PTYPE_HUB 1 +#define IDH_PTYPE_PERIPH 2 +#define IDH_PTYPE_PCABLE 3 +#define IDH_PTYPE_ACABLE 4 +#define IDH_PTYPE_AMA 5 + +#define IDH_PTYPE_DFP_UNDEF 0 +#define IDH_PTYPE_DFP_HUB 1 +#define IDH_PTYPE_DFP_HOST 2 +#define IDH_PTYPE_DFP_PB 3 +#define IDH_PTYPE_DFP_AMC 4 + +#define VDO_IDH(usbh, usbd, ptype, ptype_dfp, is_modal, vid) \ + ((usbh) << 31 | (usbd) << 30 | ((ptype) & 0x7) << 27 \ + | ((ptype_dfp) & 0x07) << 23 \ + | (is_modal) << 26 | ((vid) & 0xffff)) + +#define VDO_IDH_PD20(idh) (idh & (~(0x1f << 21))) + +#define PD_IDH_PTYPE(vdo) (((vdo) >> 27) & 0x7) +#define PD_IDH_PTYPE_DFP(vdo) (((vdo) >> 23) & 0x7) +#define PD_IDH_VID(vdo) ((vdo) & 0xffff) + +#define PD_IDH_VID_MASK (0xffff) + +#define PD_IDH_MODAL_SUPPORT (1<<26) + +/* + * Cert Stat VDO + * ------------- + * <31:20> : SBZ + * <19:0> : USB-IF assigned TID for this cable + */ +#define VDO_CSTAT(tid) ((tid) & 0xfffff) +#define PD_CSTAT_TID(vdo) ((vdo) & 0xfffff) + +/* + * Product VDO + * ----------- + * <31:16> : USB Product ID + * <15:0> : USB bcdDevice + */ +#define VDO_PRODUCT(pid, bcd) (((pid) & 0xffff) << 16 | ((bcd) & 0xffff)) +#define PD_PRODUCT_BCD(vdo) ((vdo) & 0xffff) +#define PD_PRODUCT_PID(vdo) (((vdo) >> 16) & 0xffff) + +/* + * Cable VDO + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:20> :: SBZ + * <19:18> :: type-C to Type-A/B/C (00b == A, 01 == B, 10 == C) + * <17> :: Type-C to Plug/Receptacle (0b == plug, 1b == receptacle) + * <16:13> :: cable latency (0001 == <10ns(~1m length)) + * <12:11> :: cable termination type (11b == both ends active VCONN req) + * <10> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) + * <9> :: SSTX2 Directionality support + * <8> :: SSRX1 Directionality support + * <7> :: SSRX2 Directionality support + * <6:5> :: Vbus current handling capability + * <4> :: Vbus through cable (0b == no, 1b == yes) + * <3> :: SOP" controller present? (0b == no, 1b == yes) + * <2:0> :: USB SS Signaling support + */ +#define CABLE_ATYPE 0 +#define CABLE_BTYPE 1 +#define CABLE_CTYPE 2 +#define CABLE_PLUG 0 +#define CABLE_RECEPTACLE 1 +#define CABLE_CURR_1A5 0 +#define CABLE_CURR_3A 1 +#define CABLE_CURR_5A 2 +#define CABLE_USBSS_U2_ONLY 0 +#define CABLE_USBSS_U31_GEN1 1 +#define CABLE_USBSS_U31_GEN2 2 +#define VDO_CABLE(hw, fw, cbl, gdr, lat, term, tx1d,\ + tx2d, rx1d, rx2d, cur, vps, sopp, usbss) \ + (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 | ((cbl) & 0x3) << 18 \ + | (gdr) << 17 | ((lat) & 0x7) << 13 | ((term) & 0x3) << 11 \ + | (tx1d) << 10 | (tx2d) << 9 | (rx1d) << 8 | (rx2d) << 7 \ + | ((cur) & 0x3) << 5 | (vps) << 4 | (sopp) << 3 \ + | ((usbss) & 0x7)) + +#define PD_VDO_CABLE_CURR(x) (((x) >> 5) & 0x03) + +/* + * AMA VDO + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:12> :: SBZ + * <11> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) + * <10> :: SSTX2 Directionality support + * <9> :: SSRX1 Directionality support + * <8> :: SSRX2 Directionality support + * <7:5> :: Vconn power + * <4> :: Vconn power required + * <3> :: Vbus power required + * <2:0> :: USB SS Signaling support + */ +#define VDO_AMA(hw, fw, tx1d, tx2d, rx1d, rx2d, vcpwr, vcr, vbr, usbss) \ + (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 \ + | (tx1d) << 11 | (tx2d) << 10 | (rx1d) << 9 | (rx2d) << 8 \ + | ((vcpwr) & 0x3) << 5 | (vcr) << 4 | (vbr) << 3 \ + | ((usbss) & 0x7)) + +#define PD_VDO_AMA_VCONN_REQ(vdo) (((vdo) >> 4) & 1) +#define PD_VDO_AMA_VBUS_REQ(vdo) (((vdo) >> 3) & 1) + +#define AMA_VCONN_PWR_1W 0 +#define AMA_VCONN_PWR_1W5 1 +#define AMA_VCONN_PWR_2W 2 +#define AMA_VCONN_PWR_3W 3 +#define AMA_VCONN_PWR_4W 4 +#define AMA_VCONN_PWR_5W 5 +#define AMA_VCONN_PWR_6W 6 +#define AMA_USBSS_U2_ONLY 0 +#define AMA_USBSS_U31_GEN1 1 +#define AMA_USBSS_U31_GEN2 2 +#define AMA_USBSS_BBONLY 3 + +/* + * SVDM Discover SVIDs request -> response + * + * Request is properly formatted VDM Header with discover SVIDs command. + * Response is a set of SVIDs of all all supported SVIDs with all zero's to + * mark the end of SVIDs. If more than 12 SVIDs are supported command SHOULD be + * repeated. + */ +#define VDO_SVID(svid0, svid1) (((svid0) & 0xffff) << 16 | ((svid1) & 0xffff)) +#define PD_VDO_SVID_SVID0(vdo) ((vdo) >> 16) +#define PD_VDO_SVID_SVID1(vdo) ((vdo) & 0xffff) + +/* + * DisplayPort modes capabilities + * ------------------------------- + * <31:24> : SBZ + * <23:16> : UFP_D pin assignment supported + * <15:8> : DFP_D pin assignment supported + * <7> : USB 2.0 signaling (0b=yes, 1b=no) + * <6> : Plug | Receptacle (0b == plug, 1b == receptacle) + * <5:2> : xxx1: Supports DPv1.3, xx1x Supports USB Gen 2 signaling + * Other bits are reserved. + * <1:0> : signal direction ( 00b=rsv, 01b=sink, 10b=src 11b=both ) + */ +#define VDO_MODE_DP(snkp, srcp, usb, gdr, sign, sdir) \ + (((snkp) & 0xff) << 16 | ((srcp) & 0xff) << 8 \ + | ((usb) & 1) << 7 | ((gdr) & 1) << 6 | ((sign) & 0xF) << 2 \ + | ((sdir) & 0x3)) +#define PD_DP_PIN_CAPS(x) ((((x) >> 6) & 0x1) ? (((x) >> 16) & 0x3f) \ + : (((x) >> 8) & 0x3f)) + +#define MODE_DP_PIN_A 0x01 +#define MODE_DP_PIN_B 0x02 +#define MODE_DP_PIN_C 0x04 +#define MODE_DP_PIN_D 0x08 +#define MODE_DP_PIN_E 0x10 +#define MODE_DP_PIN_F 0x20 + +/* Pin configs B/D/F support multi-function */ +#define MODE_DP_PIN_MF_MASK 0x2a +/* Pin configs A/B support BR2 signaling levels */ +#define MODE_DP_PIN_BR2_MASK 0x3 +/* Pin configs C/D/E/F support DP signaling levels */ +#define MODE_DP_PIN_DP_MASK 0x3c + +#define MODE_DP_V13 0x1 +#define MODE_DP_GEN2 0x2 + +#define MODE_DP_SNK 0x1 +#define MODE_DP_SRC 0x2 +#define MODE_DP_BOTH 0x3 + +#define MODE_DP_PORT_CAP(raw) (raw & 0x03) +#define MODE_DP_SIGNAL_SUPPORT(raw) ((raw>>2) & 0x0f) +#define MODE_DP_RECEPT(mode) ((mode >> 6) & 0x01) + +#define MODE_DP_PIN_DFP(mode) ((mode >> 8) & 0xff) +#define MODE_DP_PIN_UFP(mode) ((mode >> 16) & 0xff) + +#define PD_DP_DFP_D_PIN_CAPS(x) (MODE_DP_RECEPT(x) ? \ + MODE_DP_PIN_DFP(x) : MODE_DP_PIN_UFP(x)) + +#define PD_DP_UFP_D_PIN_CAPS(x) (MODE_DP_RECEPT(x) ? \ + MODE_DP_PIN_UFP(x) : MODE_DP_PIN_DFP(x)) + +/* + * DisplayPort Status VDO + * ---------------------- + * <31:9> : SBZ + * <8> : IRQ_HPD : 1 == irq arrived since last message otherwise 0. + * <7> : HPD state : 0 = HPD_LOW, 1 == HPD_HIGH + * <6> : Exit DP Alt mode: 0 == maintain, 1 == exit + * <5> : USB config : 0 == maintain current, 1 == switch to USB from DP + * <4> : Multi-function preference : 0 == no pref, 1 == MF preferred. + * <3> : enabled : is DPout on/off. + * <2> : power low : 0 == normal or LPM disabled, 1 == DP disabled for LPM + * <1:0> : connect status : 00b == no (DFP|UFP)_D is connected or disabled. + * 01b == DFP_D connected, 10b == UFP_D connected, 11b == both. + */ + +#define VDO_DP_STATUS(irq, lvl, amode, usbc, mf, en, lp, conn) \ + (((irq) & 1) << 8 | ((lvl) & 1) << 7 | ((amode) & 1) << 6 \ + | ((usbc) & 1) << 5 | ((mf) & 1) << 4 | ((en) & 1) << 3 \ + | ((lp) & 1) << 2 | ((conn & 0x3) << 0)) + +#define PD_VDO_DPSTS_HPD_IRQ(x) (((x) >> 8) & 1) +#define PD_VDO_DPSTS_HPD_LVL(x) (((x) >> 7) & 1) +#define PD_VDO_DPSTS_MF_PREF(x) (((x) >> 4) & 1) + +#define PD_VDO_DPSTS_CONNECT(x) (((x) >> 0) & 0x03) + +#define DPSTS_DISCONNECT 0 + +#define DPSTS_DFP_D_CONNECTED (1 << 0) +#define DPSTS_UFP_D_CONNECTED (1 << 1) +#define DPSTS_BOTH_CONNECTED (DPSTS_DFP_D_CONNECTED | DPSTS_UFP_D_CONNECTED) + +/* UFP_U only */ +#define DPSTS_DP_ENABLED (1<<3) +#define DPSTS_DP_MF_PREF (1<<4) +#define DPSTS_DP_USB_CONFIG (1<<5) +#define DPSTS_DP_EXIT_ALT_MODE (1<<6) + +/* UFP_D only */ +#define DPSTS_DP_HPD_STATUS (1<<7) +#define DPSTS_DP_HPD_IRQ (1<<8) + +/* Per DisplayPort Spec v1.3 Section 3.3 */ +#define HPD_USTREAM_DEBOUNCE_LVL (2*MSEC) +#define HPD_USTREAM_DEBOUNCE_IRQ (250) +#define HPD_DSTREAM_DEBOUNCE_IRQ (750) /* between 500-1000us */ + +/* + * DisplayPort Configure VDO + * ------------------------- + * <31:24> : SBZ + * <23:16> : SBZ + * <15:8> : Pin assignment requested. Choose one from mode caps. + * <7:6> : SBZ + * <5:2> : signalling : 1h == DP v1.3, 2h == Gen 2 + * Oh is only for USB, remaining values are reserved + * <1:0> : cfg : 00 == USB, 01 == DFP_D, 10 == UFP_D, 11 == reserved + */ + +#define DP_CONFIG_USB 0 +#define DP_CONFIG_DFP_D 1 +#define DP_CONFIG_UFP_D 2 + +#define VDO_DP_CFG(pin, sig, cfg) \ + (((pin) & 0xff) << 8 | ((sig) & 0xf) << 2 | ((cfg) & 0x3)) + +#define VDO_DP_DFP_CFG(pin, sig) VDO_DP_CFG(pin, sig, DP_CONFIG_DFP_D) +#define VDO_DP_UFP_CFG(pin, sig) VDO_DP_CFG(pin, sig, DP_CONFIG_UFP_D) + +#define PD_DP_CFG_ROLE(x) (x & 0x3) +#define PD_DP_CFG_USB(x) ((x & 0x3) == DP_CONFIG_USB) +#define PD_DP_CFG_DFP_D(x) ((x & 0x3) == DP_CONFIG_DFP_D) +#define PD_DP_CFG_UFP_D(x) ((x & 0x3) == DP_CONFIG_UFP_D) +#define PD_DP_CFG_DPON(x) (PD_DP_CFG_DFP_D(x) | PD_DP_CFG_UFP_D(x)) + +#define DP_SIG_DPV13 (0x01) +#define DP_SIG_GEN2 (0x02) + +#define DP_PIN_ASSIGN_SUPPORT_A (1 << 0) +#define DP_PIN_ASSIGN_SUPPORT_B (1 << 1) +#define DP_PIN_ASSIGN_SUPPORT_C (1 << 2) +#define DP_PIN_ASSIGN_SUPPORT_D (1 << 3) +#define DP_PIN_ASSIGN_SUPPORT_E (1 << 4) +#define DP_PIN_ASSIGN_SUPPORT_F (1 << 5) + +/* + * Get the pin assignment mask + * for backward compatibility, if it is null, + * get the former sink pin assignment we used to be in <23:16>. + */ + +#define PD_DP_CFG_PIN(x) (((x) >> 8) & 0xff) + +/* USB-IF SVIDs */ +#define USB_SID_PD 0xff00 /* power delivery */ +#define USB_SID_DISPLAYPORT 0xff01 /* display port */ +#define USB_VID_RICHTEK 0x29cf /* demo uvdm */ +#define USB_VID_DIRECTCHARGE 0x29cf /* direct charge */ + +/* PD counter definitions */ +#define PD_MESSAGE_ID_COUNT 7 +#define PD_HARD_RESET_COUNT 2 +#define PD_CAPS_COUNT 50 +#define PD_WAIT_RETRY_COUNT 1 +#define PD_DISCOVER_ID_COUNT 3 /* max : 20 */ +#define PD_DISCOVER_ID30_COUNT 2 /* max : 20 */ + +enum { + PD_WAIT_VBUS_DISABLE = 0, + PD_WAIT_VBUS_VALID_ONCE = 1, + PD_WAIT_VBUS_INVALID_ONCE = 2, + PD_WAIT_VBUS_SAFE0V_ONCE = 3, + PD_WAIT_VBUS_STABLE_ONCE = 4, +}; + +struct pd_port_power_caps { + uint8_t nr; + uint32_t pdos[7]; +}; + +struct svdm_mode { + uint8_t mode_cnt; + uint32_t mode_vdo[VDO_MAX_NR]; +}; + +struct svdm_svid_ops; +struct svdm_svid_data { + bool exist; + uint16_t svid; + uint8_t active_mode; + struct svdm_mode local_mode; + struct svdm_mode remote_mode; + const struct svdm_svid_ops *ops; +}; + +struct svdm_svid_list { + uint8_t cnt; + uint16_t svids[VDO_MAX_SVID_NR]; +}; + +struct pd_port; +typedef void (*pe_state_action_fcn_t)(struct pd_port *pd_port); + +struct dp_data { + uint8_t ufp_u_state; + uint8_t dfp_u_state; + + uint32_t local_status; + uint32_t remote_status; + + uint32_t local_config; + uint32_t remote_config; +}; + +struct pe_data { /* reset after detached */ + bool pd_connected; + bool pd_prev_connected; + bool explicit_contract; + bool invalid_contract; + bool modal_operation; + + bool pe_ready; + bool reset_vdm_state; + bool power_cable_present; + bool during_swap; /* pr or dr swap */ + +#ifdef CONFIG_USB_PD_REV30 + bool cable_rev_discovered; +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_KEEP_PARTNER_ID + bool partner_id_present; +#endif /* CONFIG_USB_PD_KEEP_PARTNER_ID */ + +#ifdef CONFIG_USB_PD_VCONN_SAFE5V_ONLY + bool vconn_highv_prot; + uint8_t vconn_highv_prot_role; +#endif /* CONFIG_USB_PD_VCONN_SAFE5V_ONLY */ + + uint8_t cap_counter; + uint8_t discover_id_counter; + uint8_t hard_reset_counter; + + bool vdm_discard_retry_flag; + uint8_t vdm_discard_retry_count; + + uint8_t msg_id_rx[PD_SOP_NR]; + uint8_t msg_id_tx[PD_SOP_NR]; + + uint8_t dpm_reaction_retry; + uint8_t dpm_svdm_retry_cnt; + uint16_t dpm_flags; + uint32_t dpm_reaction_id; + uint32_t dpm_ready_reactions; + + uint8_t local_selected_cap; + uint8_t remote_selected_cap; + + uint8_t pe_state_flags; + uint8_t pe_state_flags2; + uint8_t vdm_state_flags; + uint32_t pe_state_timer; + uint32_t vdm_state_timer; + +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER + uint8_t recv_hard_reset_count; +#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + +#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER + uint8_t renegotiation_count; +#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */ + +#ifdef CONFIG_USB_PD_WAIT_AUTO_RETRY + uint8_t wait_retry_cnt; + uint8_t wait_retry_type; +#endif /* CONFIG_USB_PD_WAIT_AUTO_RETRY */ + +#ifdef CONFIG_USB_PD_KEEP_PARTNER_ID + uint32_t partner_vdos[VDO_MAX_NR]; +#endif /* CONFIG_USB_PD_KEEP_PARTNER_ID */ + + uint32_t cable_vdos[VDO_MAX_NR]; + + struct pd_port_power_caps remote_src_cap; + struct pd_port_power_caps remote_snk_cap; + +#ifdef CONFIG_USB_PD_KEEP_SVIDS + struct svdm_svid_list remote_svid_list; +#endif /* CONFIG_USB_PD_KEEP_SVIDS */ + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + uint32_t remote_alert; +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + uint32_t local_alert; + bool get_status_once; +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + bool pd_traffic_idle; /* source only */ + uint8_t pd_traffic_control; +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + bool dc_pps_mode; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +#ifdef CONFIG_USB_PD_ALT_MODE + struct dp_data dp_data; +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + uint8_t pd_status_event; +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +}; + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO +struct pd_battery_info { + uint32_t bat_status; + struct pd_manufacturer_info mfrs_info; + struct pd_battery_capabilities bat_cap; +}; +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY +struct pd_country_authority { + uint16_t code; + uint8_t len; + uint8_t *data; +}; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY */ + +struct pd_port { + struct tcpc_device *tcpc; + struct mutex pd_lock; + + /* PD */ + bool msg_output_lock; + + uint8_t state_machine; + uint8_t pd_connect_state; + + uint8_t pe_pd_state; + uint8_t pe_vdm_state; + + uint8_t pe_state_next; + uint8_t pe_state_curr; + + uint8_t data_role; + uint8_t power_role; + uint8_t vconn_role; + + struct pe_data pe_data; + +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE + uint8_t error_recovery_once; +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + +#ifdef CONFIG_USB_PD_REV30 + struct wakeup_source *pps_request_wake_lock; + wait_queue_head_t pps_request_wait_que; + atomic_t pps_request; + struct task_struct *pps_request_task; +#ifdef CONFIG_USB_PD_REV30_SYNC_SPEC_REV + uint8_t pd_revision[2]; +#endif /* CONFIG_USB_PD_REV30_SYNC_SPEC_REV */ +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP + uint8_t msg_id_pr_swap_last; +#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */ + + uint32_t last_rdo; + + uint8_t id_vdo_nr; + uint32_t id_vdos[VDO_MAX_NR]; + + uint32_t id_header; + + uint8_t svid_data_cnt; + struct svdm_svid_data *svid_data; + +/* DPM */ + int request_v; + int request_i; + int request_v_new; + int request_i_new; + int request_i_op; + int request_i_max; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + int request_v_apdo; + int request_i_apdo; + bool request_apdo; + bool request_apdo_new; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + struct pd_port_power_caps local_src_cap; + struct pd_port_power_caps local_snk_cap; + struct pd_port_power_caps local_src_cap_default; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + uint8_t local_snk_cap_nr_pd30; + uint8_t local_snk_cap_nr_pd20; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + uint16_t mode_svid; + uint8_t mode_obj_pos; + + uint32_t dpm_caps; + + uint8_t dpm_charging_policy; + uint8_t dpm_charging_policy_default; + +/* ALT Mode */ +#ifdef CONFIG_USB_PD_ALT_MODE + uint8_t dp_first_connected; + uint8_t dp_second_connected; +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + bool uvdm_wait_resp; + uint8_t uvdm_cnt; + uint16_t uvdm_svid; + uint32_t uvdm_data[PD_DATA_OBJ_SIZE]; +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC + uint8_t dc_dfp_state; + uint32_t dc_pass_code; +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + bool custom_dbgacc; +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + +#ifdef CONFIG_USB_PD_RICHTEK_UVDM + bool richtek_init_done; +#endif /* CONFIG_USB_PD_RICHTEK_UVDM */ + + struct tcp_dpm_event tcp_event; + uint8_t tcp_event_id_1st; + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + uint8_t tcp_event_id_2nd; + bool tcp_event_drop_reset_once; + + uint8_t *tcpm_bk_cb_data; + uint8_t tcpm_bk_cb_data_max; +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + +#ifdef CONFIG_USB_PD_BLOCK_TCPM + int tcpm_bk_ret; + bool tcpm_bk_done; + uint8_t tcpm_bk_event_id; + struct mutex tcpm_bk_lock; + wait_queue_head_t tcpm_bk_wait_que; +#endif /* CONFIG_USB_PD_BLOCK_TCPM */ + + /* curr_event info */ + bool curr_is_vdm_evt; + struct pd_event curr_pd_event; + + uint16_t pd_msg_data_size; + uint8_t pd_msg_data_count; + uint8_t *pd_msg_data_payload; + + uint8_t curr_vdm_ops; + uint16_t curr_vdm_svid; + + uint16_t curr_msg_hdr; + uint32_t curr_vdm_hdr; + + uint8_t curr_ready_state; + uint8_t curr_hreset_state; + uint8_t curr_sreset_state; + + bool curr_unsupported_msg; + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + struct pd_source_cap_ext src_cap_ext; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + struct pd_manufacturer_info mfrs_info; +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO + uint32_t pid; + uint32_t vid; + uint8_t bat_nr; + struct pd_battery_info *fix_bat_info; + struct pd_battery_info *swap_bat_info; +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + uint8_t pd_status_bat_in; + uint8_t pd_status_present_in; + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL_TEMP + uint8_t pd_status_temp; + uint8_t pd_status_temp_status; +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL_TEMP */ +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY + uint8_t country_nr; + struct pd_country_authority *country_info; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY */ + +#ifdef CONFIG_RECV_BAT_ABSENT_NOTIFY + /* for MTK only, handle battery plug out */ + struct work_struct fg_bat_work; + struct notifier_block fg_bat_nb; +#endif /* CONFIG_RECV_BAT_ABSENT_NOTIFY */ + + uint8_t cap_miss_match; /* For src_cap miss match */ +}; + +#ifdef CONFIG_USB_PD_ALT_MODE +static inline struct dp_data *pd_get_dp_data(struct pd_port *pd_port) +{ + return &pd_port->pe_data.dp_data; +} +#endif /* CONFIG_USB_PD_ALT_MODE */ + +extern int pd_core_init(struct tcpc_device *tcpc); + +static inline void *pd_get_msg_data_payload(struct pd_port *pd_port) +{ + return pd_port->pd_msg_data_payload; +} + +static inline uint32_t *pd_get_msg_vdm_data_payload(struct pd_port *pd_port) +{ + uint32_t *payload = (uint32_t *) pd_port->pd_msg_data_payload; + + if (payload == NULL) + return NULL; + + return payload+1; +} + +static inline uint8_t pd_get_msg_data_count(struct pd_port *pd_port) +{ + return pd_port->pd_msg_data_count; +} + +static inline uint16_t pd_get_msg_data_size(struct pd_port *pd_port) +{ + return pd_port->pd_msg_data_size; +} + +static inline uint8_t pd_get_msg_vdm_data_count(struct pd_port *pd_port) +{ + return pd_port->pd_msg_data_count-1; +} + +static inline bool pd_curr_is_vdm_evt(struct pd_port *pd_port) +{ + return pd_port->curr_is_vdm_evt; +} + +static inline struct pd_event *pd_get_curr_pd_event(struct pd_port *pd_port) +{ + return &pd_port->curr_pd_event; +} + +static inline uint32_t pd_get_msg_vdm_hdr(struct pd_port *pd_port) +{ + return pd_port->curr_vdm_hdr; +} + +#ifdef CONFIG_USB_PD_REV30 +static inline uint8_t pd_get_msg_hdr_ext(struct pd_port *pd_port) +{ + return PD_HEADER_EXT(pd_port->curr_msg_hdr); +} +#endif /* CONFIG_USB_PD_REV30 */ + +static inline uint8_t pd_get_msg_hdr_rev(struct pd_port *pd_port) +{ + return PD_HEADER_REV(pd_port->curr_msg_hdr); +} + +static inline uint8_t pd_get_msg_hdr_cnt(struct pd_port *pd_port) +{ + return PD_HEADER_CNT(pd_port->curr_msg_hdr); +} + +static inline uint8_t pd_get_msg_hdr_id(struct pd_port *pd_port) +{ + return PD_HEADER_ID(pd_port->curr_msg_hdr); +} + +static inline bool pd_check_ctrl_msg_event( + struct pd_port *pd_port, uint8_t msg) +{ + return pd_event_ctrl_msg_match( + pd_get_curr_pd_event(pd_port), msg); +} + +static inline bool pd_check_data_msg_event( + struct pd_port *pd_port, uint8_t msg) +{ + return pd_event_data_msg_match( + pd_get_curr_pd_event(pd_port), msg); +} + +static inline bool pd_check_timer_msg_event( + struct pd_port *pd_port, uint8_t msg) +{ + return pd_event_timer_msg_match( + pd_get_curr_pd_event(pd_port), msg); +} + +extern bool pd_is_reset_cable(struct pd_port *pd_port); +extern bool pd_is_discover_cable(struct pd_port *pd_port); + +static inline int pd_is_support_modal_operation(struct pd_port *pd_port) +{ + if (!(pd_port->id_vdos[0] & PD_IDH_MODAL_SUPPORT)) + return false; + + return pd_port->svid_data_cnt > 0; +} + +static inline int pd_is_source_support_apdo(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + uint8_t i; + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + + /* index0 always be fixed 5V */ + for (i = 1; i < src_cap->nr; i++) { + if (PDO_TYPE(src_cap->pdos[i]) == PDO_TYPE_APDO) + return true; + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + return false; +} + +/* new definitions*/ + +#define PD_RX_CAP_PE_IDLE (0) +#define PD_RX_CAP_PE_DISABLE (TCPC_RX_CAP_HARD_RESET) +#define PD_RX_CAP_PE_STARTUP (TCPC_RX_CAP_HARD_RESET) +#define PD_RX_CAP_PE_HARDRESET (0) +#define PD_RX_CAP_PE_SWAP \ + (TCPC_RX_CAP_HARD_RESET|TCPC_RX_CAP_SOP) +#define PD_RX_CAP_PE_SEND_WAIT_CAP \ + (TCPC_RX_CAP_HARD_RESET|TCPC_RX_CAP_SOP) +#define PD_RX_CAP_PE_DISCOVER_CABLE \ + (TCPC_RX_CAP_HARD_RESET|TCPC_RX_CAP_SOP_PRIME) +#define PD_RX_CAP_PE_READY_UFP \ + (TCPC_RX_CAP_HARD_RESET|TCPC_RX_CAP_SOP) + +#ifdef CONFIG_PD_DISCOVER_CABLE_ID +#define PD_RX_CAP_PE_READY_DFP \ + (TCPC_RX_CAP_HARD_RESET|TCPC_RX_CAP_SOP|TCPC_RX_CAP_SOP_PRIME) +#else +#define PD_RX_CAP_PE_READY_DFP (TCPC_RX_CAP_HARD_RESET|TCPC_RX_CAP_SOP) +#endif + +enum { + PD_BIST_MODE_DISABLE = 0, + PD_BIST_MODE_EVENT_PENDING, + PD_BIST_MODE_TEST_DATA, +}; + +void pd_reset_svid_data(struct pd_port *pd_port); +int pd_reset_protocol_layer(struct pd_port *pd_port, bool sop_only); + +int pd_set_rx_enable(struct pd_port *pd_port, uint8_t enable); + +int pd_enable_vbus_valid_detection(struct pd_port *pd_port, bool wait_valid); +int pd_enable_vbus_safe0v_detection(struct pd_port *pd_port); +int pd_enable_vbus_stable_detection(struct pd_port *pd_port); + +uint32_t pd_reset_pdo_power(struct tcpc_device *tcpc, + uint32_t pdo, uint32_t imax); + +void pd_extract_rdo_power( + uint32_t rdo, uint32_t pdo, uint32_t *op_curr, uint32_t *max_curr); + +uint32_t pd_get_cable_curr_lvl(struct pd_port *pd_port); +uint32_t pd_get_cable_current_limit(struct pd_port *pd_port); + + +int pd_set_data_role(struct pd_port *pd_port, uint8_t dr); +int pd_set_power_role(struct pd_port *pd_port, uint8_t pr); +int pd_init_message_hdr(struct pd_port *pd_port, bool act_as_sink); + +int pd_set_cc_res(struct pd_port *pd_port, int pull); +int pd_set_vconn(struct pd_port *pd_port, uint8_t role); +int pd_reset_local_hw(struct pd_port *pd_port); + +int pd_enable_bist_test_mode(struct pd_port *pd_port, bool en); +int pd_schedule_wait_request(struct pd_port *pd_port, uint8_t type); +int pd_cancel_wait_request(struct pd_port *pd_port); + +void pd_lock_msg_output(struct pd_port *pd_port); +void pd_unlock_msg_output(struct pd_port *pd_port); + +int pd_update_connect_state(struct pd_port *pd_port, uint8_t state); + +/* ---- PD notify TCPC Policy Engine State Changed ---- */ + +extern void pd_try_put_pe_idle_event(struct pd_port *pd_port); +extern void pd_notify_pe_transit_to_default(struct pd_port *pd_port); +extern void pd_notify_pe_hard_reset_completed(struct pd_port *pd_port); +extern void pd_notify_pe_send_hard_reset(struct pd_port *pd_port); +extern void pd_notify_pe_idle(struct pd_port *pd_port); +extern void pd_notify_pe_wait_vbus_once(struct pd_port *pd_port, int wait_evt); +extern void pd_notify_pe_error_recovery(struct pd_port *pd_port); +extern void pd_notify_pe_execute_pr_swap( + struct pd_port *pd_port, bool start_swap); +extern void pd_notify_pe_cancel_pr_swap(struct pd_port *pd_port); +extern void pd_notify_pe_reset_protocol(struct pd_port *pd_port); +extern void pd_noitfy_pe_bist_mode(struct pd_port *pd_port, uint8_t mode); +extern void pd_notify_pe_pr_changed(struct pd_port *pd_port); +extern void pd_notify_pe_snk_explicit_contract(struct pd_port *pd_port); +extern void pd_notify_pe_src_explicit_contract(struct pd_port *pd_port); +extern void pd_notify_pe_transmit_msg(struct pd_port *pd_port, uint8_t type); + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE +extern void pd_notify_pe_direct_charge(struct pd_port *pd_port, bool en); +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER +extern void pd_notify_pe_over_recv_hreset(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + +extern void pd_notify_tcp_event_buf_reset( + struct pd_port *pd_port, uint8_t reason); +extern void pd_notify_tcp_event_1st_result(struct pd_port *pd_port, int ret); +extern void pd_notify_tcp_event_2nd_result(struct pd_port *pd_port, int ret); +extern void pd_notify_tcp_vdm_event_2nd_result( + struct pd_port *pd_port, uint8_t ret); + +/* ---- pd_timer ---- */ + +static inline void pd_restart_timer(struct pd_port *pd_port, uint32_t timer_id) +{ + return tcpc_restart_timer(pd_port->tcpc, timer_id); +} + +static inline void pd_enable_timer(struct pd_port *pd_port, uint32_t timer_id) +{ + return tcpc_enable_timer(pd_port->tcpc, timer_id); +} + +static inline void pd_enable_pe_state_timer( + struct pd_port *pd_port, uint32_t timer_id) +{ + /* timer_id shoult not be zero */ + PD_BUG_ON(timer_id == 0); + + pd_port->pe_data.pe_state_timer = timer_id; + return pd_enable_timer(pd_port, timer_id); +} + +static inline void pd_enable_vdm_state_timer( + struct pd_port *pd_port, uint32_t timer_id) +{ + pd_port->pe_data.vdm_state_timer = timer_id; +} + +static inline void pd_disable_timer(struct pd_port *pd_port, uint32_t timer_id) +{ + return tcpc_disable_timer(pd_port->tcpc, timer_id); +} + +static inline void pd_disable_pe_state_timer(struct pd_port *pd_port) +{ + struct pe_data *pe_data = &pd_port->pe_data; + + pd_disable_timer(pd_port, pe_data->pe_state_timer); + pe_data->pe_state_timer = 0; +} + +void pd_reset_pe_timer(struct pd_port *pd_port); + +/* ---- pd_event ---- */ + +static inline bool pd_put_pe_event(struct pd_port *pd_port, uint8_t pe_event) +{ + struct pd_event evt = { + .event_type = PD_EVT_PE_MSG, + .msg = pe_event, + .pd_msg = NULL, + }; + + return pd_put_event(pd_port->tcpc, &evt, false); +} + +static inline bool pd_put_dpm_notify_event( + struct pd_port *pd_port, uint8_t notify) +{ + struct pd_event evt = { + .event_type = PD_EVT_DPM_MSG, + .msg = PD_DPM_NOTIFIED, + .msg_sec = notify, + .pd_msg = NULL, + }; + + return pd_put_event(pd_port->tcpc, &evt, false); +} + +static inline bool pd_put_dpm_ack_event(struct pd_port *pd_port) +{ + struct pd_event evt = { + .event_type = PD_EVT_DPM_MSG, + .msg = PD_DPM_ACK, + .pd_msg = NULL, + }; + + return pd_put_event(pd_port->tcpc, &evt, false); +} + +static inline bool pd_put_dpm_nak_event(struct pd_port *pd_port, uint8_t notify) +{ + struct pd_event evt = { + .event_type = PD_EVT_DPM_MSG, + .msg = PD_DPM_NAK, + .msg_sec = notify, + .pd_msg = NULL, + }; + + return pd_put_event(pd_port->tcpc, &evt, false); +} + +static inline bool pd_put_dpm_event(struct pd_port *pd_port, uint8_t msg) +{ + struct pd_event evt = { + .event_type = PD_EVT_DPM_MSG, + .msg = msg, + .pd_msg = NULL, + }; + + return pd_put_event(pd_port->tcpc, &evt, false); +} + +static inline bool pd_put_tcp_pd_event(struct pd_port *pd_port, uint8_t event) +{ + struct pd_event evt = { + .event_type = PD_EVT_TCP_MSG, + .msg = event, + .msg_sec = PD_TCP_FROM_PE, + .pd_msg = NULL, + }; + + return pd_put_event(pd_port->tcpc, &evt, false); +}; + +static inline bool pd_put_tcp_vdm_event(struct pd_port *pd_port, uint8_t event) +{ + struct pd_event evt = { + .event_type = PD_EVT_TCP_MSG, + .msg = event, + .msg_sec = PD_TCP_FROM_PE, + .pd_msg = NULL, + }; + + return pd_put_vdm_event(pd_port->tcpc, &evt, false); +}; + +static inline bool vdm_put_hw_event( + struct tcpc_device *tcpc, uint8_t hw_event) +{ + struct pd_event evt = { + .event_type = PD_EVT_HW_MSG, + .msg = hw_event, + .pd_msg = NULL, + }; + + return pd_put_vdm_event(tcpc, &evt, false); +} + +static inline bool vdm_put_pe_event( + struct tcpc_device *tcpc, uint8_t pe_event) +{ + struct pd_event evt = { + .event_type = PD_EVT_PE_MSG, + .msg = pe_event, + .pd_msg = NULL, + }; + + return pd_put_vdm_event(tcpc, &evt, false); +} + +static inline bool vdm_put_dpm_discover_cable_event(struct pd_port *pd_port) +{ + /* waiting for dpm_ack event */ + return pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_DISCOVER_CABLE); +} + +static inline bool pd_put_hw_event( + struct tcpc_device *tcpc, uint8_t hw_event) +{ + struct pd_event evt = { + .event_type = PD_EVT_HW_MSG, + .msg = hw_event, + .pd_msg = NULL, + }; + + return pd_put_event(tcpc, &evt, false); +} + +static inline bool pd_put_sink_tx_event( + struct tcpc_device *tcpc, uint8_t cc_res) +{ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + struct pd_event evt = { + .event_type = PD_EVT_HW_MSG, + .msg = PD_HW_SINK_TX_CHANGE, + .pd_msg = NULL, + }; + + evt.msg_sec = cc_res == TYPEC_CC_VOLT_SNK_3_0; + return pd_put_event(tcpc, &evt, false); +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + + return true; +} + +/* ---- Handle PD Message ----*/ + +void pd_handle_first_pd_command(struct pd_port *pd_port); +int pd_handle_soft_reset(struct pd_port *pd_port); +void pd_handle_hard_reset_recovery(struct pd_port *pd_port); + +/* ---- Send PD Message ----*/ + +enum tcpm_transmit_type { + TCPC_TX_SOP = 0, + TCPC_TX_SOP_PRIME = 1, + TCPC_TX_SOP_PRIME_PRIME = 2, + TCPC_TX_SOP_DEBUG_PRIME = 3, + TCPC_TX_SOP_DEBUG_PRIME_PRIME = 4, + TCPC_TX_HARD_RESET = 5, + TCPC_TX_CABLE_RESET = 6, + TCPC_TX_BIST_MODE_2 = 7 +}; + +int pd_send_message(struct pd_port *pd_port, uint8_t sop_type, + uint8_t msg, bool ext, uint16_t count, const uint32_t *data); + +int pd_send_data_msg(struct pd_port *pd_port, + uint8_t sop_type, uint8_t msg, uint8_t cnt, uint32_t *payload); + +int pd_send_sop_ctrl_msg(struct pd_port *pd_port, uint8_t msg); + +int pd_send_sop_prime_ctrl_msg(struct pd_port *pd_port, uint8_t msg); + +int pd_send_sop_data_msg(struct pd_port *pd_port, + uint8_t msg, uint8_t cnt, const uint32_t *payload); + +int pd_reply_wait_reject_msg(struct pd_port *pd_port); +int pd_reply_wait_reject_msg_no_resp(struct pd_port *pd_port); + +int pd_send_swap_msg(struct pd_port *pd_port, uint8_t msg); + +#ifdef CONFIG_USB_PD_REV30 +int pd_send_ext_msg(struct pd_port *pd_port, + uint8_t sop_type, uint8_t msg, bool request, + uint8_t chunk_nr, uint8_t size, const uint8_t *data); + +static inline int pd_send_sop_ext_msg(struct pd_port *pd_port, + uint8_t msg, uint8_t size, const void *data) +{ + PD_BUG_ON(size > MAX_EXTENDED_MSG_LEGACY_LEN); + + /* TODO: chunking */ + + return pd_send_ext_msg(pd_port, + TCPC_TX_SOP, msg, false, 0, size, data); +} +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_RESET_CABLE +int pd_send_cable_soft_reset(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_RESET_CABLE */ + +int pd_send_soft_reset(struct pd_port *pd_port); +int pd_send_hard_reset(struct pd_port *pd_port); + +int pd_send_bist_mode2(struct pd_port *pd_port); +int pd_disable_bist_mode2(struct pd_port *pd_port); + + +/* ---- Send / Reply SVDM Command ----*/ + +/* Auto enable timer if success */ +int pd_send_svdm_request(struct pd_port *pd_port, + uint8_t sop_type, uint16_t svid, uint8_t vdm_cmd, + uint8_t obj_pos, uint8_t cnt, uint32_t *data_obj, + uint32_t timer_id); + +int pd_reply_svdm_request(struct pd_port *pd_port, + uint8_t reply, uint8_t cnt, uint32_t *data_obj); + +#ifdef CONFIG_USB_POWER_DELIVERY + +static inline int pd_send_vdm_discover_id( + struct pd_port *pd_port, uint8_t sop_type) +{ + return pd_send_svdm_request(pd_port, sop_type, USB_SID_PD, + CMD_DISCOVER_IDENT, 0, 0, NULL, PD_TIMER_VDM_RESPONSE); +} + +static inline int pd_send_vdm_discover_svids( + struct pd_port *pd_port, uint8_t sop_type) +{ + return pd_send_svdm_request(pd_port, sop_type, USB_SID_PD, + CMD_DISCOVER_SVID, 0, 0, NULL, PD_TIMER_VDM_RESPONSE); +} + +static inline int pd_send_vdm_discover_modes( + struct pd_port *pd_port, uint8_t sop_type, uint16_t svid) +{ + return pd_send_svdm_request(pd_port, sop_type, svid, + CMD_DISCOVER_MODES, 0, 0, NULL, PD_TIMER_VDM_RESPONSE); +} + +static inline int pd_send_vdm_enter_mode(struct pd_port *pd_port, + uint8_t sop_type, uint16_t svid, uint8_t obj_pos) +{ + return pd_send_svdm_request(pd_port, sop_type, svid, + CMD_ENTER_MODE, obj_pos, 0, NULL, PD_TIMER_VDM_MODE_ENTRY); +} + +static inline int pd_send_vdm_exit_mode(struct pd_port *pd_port, + uint8_t sop_type, uint16_t svid, uint8_t obj_pos) +{ + return pd_send_svdm_request(pd_port, sop_type, svid, + CMD_EXIT_MODE, obj_pos, 0, NULL, PD_TIMER_VDM_MODE_EXIT); +} + +static inline int pd_send_vdm_attention(struct pd_port *pd_port, + uint8_t sop_type, uint16_t svid, uint8_t obj_pos) +{ + return pd_send_svdm_request(pd_port, sop_type, svid, + CMD_ATTENTION, obj_pos, 0, NULL, 0); +} + +static inline int pd_send_vdm_dp_attention(struct pd_port *pd_port, + uint8_t sop_type, uint8_t obj_pos, uint32_t dp_status) +{ + return pd_send_svdm_request(pd_port, sop_type, USB_SID_DISPLAYPORT, + CMD_ATTENTION, obj_pos, 1, &dp_status, 0); +} + +static inline int pd_send_vdm_dp_status(struct pd_port *pd_port, + uint8_t sop_type, uint8_t obj_pos, uint8_t cnt, uint32_t *data_obj) +{ + return pd_send_svdm_request(pd_port, sop_type, USB_SID_DISPLAYPORT, + CMD_DP_STATUS, obj_pos, cnt, data_obj, PD_TIMER_VDM_RESPONSE); +} + +static inline int pd_send_vdm_dp_config(struct pd_port *pd_port, + uint8_t sop_type, uint8_t obj_pos, uint8_t cnt, uint32_t *data_obj) +{ + return pd_send_svdm_request(pd_port, sop_type, USB_SID_DISPLAYPORT, + CMD_DP_CONFIG, obj_pos, cnt, data_obj, PD_TIMER_VDM_RESPONSE); +} + +static inline int pd_reply_svdm_request_simply( + struct pd_port *pd_port, uint8_t reply) +{ + return pd_reply_svdm_request(pd_port, reply, 0, NULL); +} + +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM +int pd_send_custom_vdm(struct pd_port *pd_port, uint8_t sop_type); +int pd_reply_custom_vdm(struct pd_port *pd_port, uint8_t sop_type, + uint8_t cnt, uint32_t *payload); +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +#ifdef CONFIG_USB_PD_REV30 + +enum { /* pd_traffic_control */ + PD_SINK_TX_OK = 0, + PD_SINK_TX_NG = 1, + PD_SOURCE_TX_OK = 2, + PD_SOURCE_TX_START = 3, + PD_SINK_TX_START = 4, +}; + +#define PD30_SINK_TX_OK TYPEC_CC_RP_3_0 +#define PD30_SINK_TX_NG TYPEC_CC_RP_1_5 + +void pd_set_sink_tx(struct pd_port *pd_port, uint8_t cc); +void pd_sync_sop_spec_revision(struct pd_port *pd_port); +void pd_sync_sop_prime_spec_revision(struct pd_port *pd_port, uint8_t rev); +bool pd_is_multi_chunk_msg(struct pd_port *pd_port); + +static inline uint8_t pd_get_fix_battery_nr(struct pd_port *pd_port) +{ + return PD_SCEDB_FIX_BAT_NR(pd_port->bat_nr); +} + +static inline uint8_t pd_get_swap_battery_nr(struct pd_port *pd_port) +{ + return PD_SCEDB_SWAP_BAT_NR(pd_port->bat_nr); +} + +struct pd_battery_info *pd_get_battery_info( + struct pd_port *pd_port, enum pd_battery_reference ref); +#endif /* CONFIG_USB_PD_REV30 */ + +#endif /* PD_CORE_H_ */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/pd_dbg_info.h b/drivers/usb/typec/mmi_tcpc/inc/pd_dbg_info.h new file mode 100644 index 000000000000..cd32a7852343 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/pd_dbg_info.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PD_DBG_INFO_H_INCLUDED +#define PD_DBG_INFO_H_INCLUDED + +#include +#include +#include "tcpci_config.h" + +#ifdef CONFIG_PD_DBG_INFO +extern int pd_dbg_info(const char *fmt, ...); +extern void pd_dbg_info_lock(void); +extern void pd_dbg_info_unlock(void); +extern int pd_dbg_info_init(void); +extern void pd_dbg_info_exit(void); +#else +static inline int pd_dbg_info(const char *fmt, ...) +{ + return 0; +} +static inline void pd_dbg_info_lock(void) {} +static inline void pd_dbg_info_unlock(void) {} +static inline int pd_dbg_info_init(void) +{ + return 0; +} +static inline void pd_dbg_info_exit(void) {} +#endif /* CONFIG_PD_DBG_INFO */ + +#endif /* PD_DBG_INFO_H_INCLUDED */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/pd_dpm_core.h b/drivers/usb/typec/mmi_tcpc/inc/pd_dpm_core.h new file mode 100644 index 000000000000..b200475da6e5 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/pd_dpm_core.h @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PD_DPM_CORE_H +#define PD_DPM_CORE_H + +#include "tcpci.h" +#include "pd_core.h" +#include "pd_process_evt.h" + +/* ---- MISC ---- */ +int pd_dpm_core_init(struct pd_port *pd_port); +int pd_dpm_enable_vconn(struct pd_port *pd_port, bool en); +int pd_dpm_send_sink_caps(struct pd_port *pd_port); +int pd_dpm_send_source_caps(struct pd_port *pd_port); + +void pd_dpm_inform_cable_id(struct pd_port *pd_port, bool src_startup); + +void pd_dpm_dynamic_enable_vconn(struct pd_port *pd_port); +void pd_dpm_dynamic_disable_vconn(struct pd_port *pd_port); + +/* ---- SNK ---- */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +void pd_dpm_start_pps_request_thread(struct pd_port *pd_port, bool en); +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +int pd_dpm_update_tcp_request(struct pd_port *pd_port, + struct tcp_dpm_pd_request *pd_req); + +int pd_dpm_update_tcp_request_ex(struct pd_port *pd_port, + struct tcp_dpm_pd_request_ex *pd_req); + +int pd_dpm_update_tcp_request_again(struct pd_port *pd_port); + +void pd_dpm_snk_evaluate_caps(struct pd_port *pd_port); +void pd_dpm_snk_standby_power(struct pd_port *pd_port); +void pd_dpm_snk_transition_power(struct pd_port *pd_port); +void pd_dpm_snk_hard_reset(struct pd_port *pd_port); + +/* ---- SRC ---- */ + +void pd_dpm_src_evaluate_request(struct pd_port *pd_port); +void pd_dpm_src_transition_power(struct pd_port *pd_port); +void pd_dpm_src_hard_reset(struct pd_port *pd_port); + +/* ---- UFP : Handle VDM Request ---- */ + +void pd_dpm_ufp_request_id_info(struct pd_port *pd_port); +void pd_dpm_ufp_request_svid_info(struct pd_port *pd_port); +void pd_dpm_ufp_request_mode_info(struct pd_port *pd_port); +void pd_dpm_ufp_request_enter_mode(struct pd_port *pd_port); +void pd_dpm_ufp_request_exit_mode(struct pd_port *pd_port); + +/* ---- UFP : DP Only ---- */ + +#ifdef CONFIG_USB_PD_ALT_MODE +int pd_dpm_ufp_request_dp_status(struct pd_port *pd_port); +int pd_dpm_ufp_request_dp_config(struct pd_port *pd_port); +void pd_dpm_ufp_send_dp_attention(struct pd_port *pd_port); +#endif + +/* ---- DFP : Inform VDM Result ---- */ + +void pd_dpm_dfp_inform_id(struct pd_port *pd_port, bool ack); +void pd_dpm_dfp_inform_svids(struct pd_port *pd_port, bool ack); +void pd_dpm_dfp_inform_modes(struct pd_port *pd_port, bool ack); +void pd_dpm_dfp_inform_enter_mode(struct pd_port *pd_port, bool ack); +void pd_dpm_dfp_inform_exit_mode(struct pd_port *pd_port); +void pd_dpm_dfp_inform_attention(struct pd_port *pd_port); + +/* ---- DFP : DP Only ---- */ + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +void pd_dpm_dfp_send_dp_status_update(struct pd_port *pd_port); +void pd_dpm_dfp_inform_dp_status_update(struct pd_port *pd_port, bool ack); + +void pd_dpm_dfp_send_dp_configuration(struct pd_port *pd_port); +void pd_dpm_dfp_inform_dp_configuration(struct pd_port *pd_port, bool ack); +#endif + +/* ---- SVDM/UVDM ---- */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + +void pd_dpm_ufp_recv_uvdm(struct pd_port *pd_port); +void pd_dpm_dfp_send_uvdm(struct pd_port *pd_port); +void pd_dpm_dfp_inform_uvdm(struct pd_port *pd_port, bool ack); + +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +/* ---- DRP : Inform PowerCap ---- */ + +void pd_dpm_dr_inform_sink_cap(struct pd_port *pd_port); +void pd_dpm_dr_inform_source_cap(struct pd_port *pd_port); + +/* ---- DRP : Data Role Swap ---- */ + +void pd_dpm_drs_evaluate_swap(struct pd_port *pd_port, uint8_t role); +void pd_dpm_drs_change_role(struct pd_port *pd_port, uint8_t role); + +/* ---- DRP : Power Role Swap ---- */ + +void pd_dpm_prs_evaluate_swap(struct pd_port *pd_port, uint8_t role); +void pd_dpm_prs_turn_off_power_sink(struct pd_port *pd_port); +void pd_dpm_prs_enable_power_source(struct pd_port *pd_port, bool en); +void pd_dpm_prs_change_role(struct pd_port *pd_port, uint8_t role); + +/* ---- DRP : Vconn Swap ---- */ + +void pd_dpm_vcs_evaluate_swap(struct pd_port *pd_port); +void pd_dpm_vcs_enable_vconn(struct pd_port *pd_port, uint8_t role); + + +/* + * PE : PD3.0 + */ + +#ifdef CONFIG_USB_PD_REV30 + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL +int pd_dpm_send_source_cap_ext(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE +void pd_dpm_inform_source_cap_ext(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL +int pd_dpm_send_battery_cap(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE +void pd_dpm_inform_battery_cap(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL +int pd_dpm_send_battery_status(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE +void pd_dpm_inform_battery_status(struct pd_port *pd_port); +#endif /* #ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL +int pd_dpm_send_mfrs_info(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE +void pd_dpm_inform_mfrs_info(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL +int pd_dpm_send_country_codes(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE +void pd_dpm_inform_country_codes(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL +int pd_dpm_send_country_info(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE +void pd_dpm_inform_country_info(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL +int pd_dpm_send_alert(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +void pd_dpm_inform_alert(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL +int pd_dpm_send_status(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE +void pd_dpm_inform_status(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +void pd_dpm_inform_pps_status(struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +void pd_dpm_inform_not_support(struct pd_port *pd_port); + +#endif /* CONFIG_USB_PD_REV30 */ + +/* PE : Notify DPM */ + +int pd_dpm_notify_pe_startup(struct pd_port *pd_port); +int pd_dpm_notify_pe_hardreset(struct pd_port *pd_port); + +/* TCPCI - VBUS Control */ + +static inline int pd_dpm_check_vbus_valid(struct pd_port *pd_port) +{ + return tcpci_check_vbus_valid(pd_port->tcpc); +} + +static inline int pd_dpm_sink_vbus(struct pd_port *pd_port, bool en) +{ + int mv = en ? TCPC_VBUS_SINK_5V : TCPC_VBUS_SINK_0V; + + return tcpci_sink_vbus(pd_port->tcpc, + TCP_VBUS_CTRL_REQUEST, mv, -1); +} + +static inline int pd_dpm_source_vbus(struct pd_port *pd_port, bool en) +{ + int mv = en ? TCPC_VBUS_SOURCE_5V : TCPC_VBUS_SOURCE_0V; + + return tcpci_source_vbus(pd_port->tcpc, + TCP_VBUS_CTRL_REQUEST, mv, -1); +} + +/* Mode Operations */ + +#ifdef CONFIG_USB_PD_ALT_MODE +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + +extern bool dp_dfp_u_notify_discover_id(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); + +extern bool dp_dfp_u_notify_discover_svid( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, bool ack); + +extern bool dp_dfp_u_notify_discover_modes( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, bool ack); + + +extern bool dp_dfp_u_notify_enter_mode(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops, bool ack); + +extern bool dp_dfp_u_notify_exit_mode( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, uint8_t ops); + +extern bool dp_dfp_u_notify_attention(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + +extern void dp_ufp_u_request_enter_mode( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, uint8_t ops); + +extern void dp_ufp_u_request_exit_mode( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, uint8_t ops); + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +extern bool dp_dfp_u_notify_pe_startup( + struct pd_port *pd_port, struct svdm_svid_data *svid_data); + +extern int dp_dfp_u_notify_pe_ready(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + +extern bool dp_reset_state( + struct pd_port *pd_port, struct svdm_svid_data *svid_data); + +extern bool dp_parse_svid_data( + struct pd_port *pd_port, struct svdm_svid_data *svid_data); +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_RICHTEK_UVDM +extern bool richtek_dfp_notify_pe_startup( + struct pd_port *pd_port, struct svdm_svid_data *svid_data); + +extern int richtek_dfp_notify_pe_ready(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + +extern bool richtek_dfp_notify_uvdm(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); +extern bool richtek_ufp_notify_uvdm(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); +#endif /* CONFIG_USB_PD_RICHTEK_UVDM */ + + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC +extern bool dc_dfp_notify_discover_id(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); + +extern bool dc_dfp_notify_discover_svid(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); + +extern bool dc_dfp_notify_discover_modes(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); + +extern bool dc_dfp_notify_enter_mode(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops, bool ack); + +extern bool dc_dfp_notify_exit_mode(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops); + +extern bool dc_dfp_notify_pe_startup( + struct pd_port *pd_port, struct svdm_svid_data *svid_data); + +extern int dc_dfp_notify_pe_ready(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + +extern bool dc_dfp_notify_uvdm(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); +extern bool dc_ufp_notify_uvdm(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + +extern bool dc_reset_state(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + +extern bool dc_parse_svid_data(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + + +/** + * pd_dpm_get_ready_reaction + * + * Get a pending event from the reaction's table definied by DPM. + * + * Returns TCP_DPM_EVT_ID if succeeded; + * Returns Zero to indicate there is no pending event. + * Returns DPM_READY_REACTION_BUSY to indicate + * waiting for previous reaction is finished. + */ + +#define DPM_READY_REACTION_BUSY (0xff) +extern uint8_t pd_dpm_get_ready_reaction(struct pd_port *pd_port); + +/* ---- DPM reactions ---- */ + + + +/* If receive reject/wait, cancel reaction */ +#define DPM_REACTION_REQUEST_PR_SWAP (1<<0) +#define DPM_REACTION_REQUEST_DR_SWAP (1<<1) +#define DPM_REACTION_GET_SINK_CAP (1<<2) +#define DPM_REACTION_GET_SOURCE_CAP (1<<3) +#define DPM_REACTION_ATTEMPT_GET_FLAG (1<<4) +#define DPM_REACTION_REQUEST_VCONN_SRC (1<<5) +#define DPM_REACTION_RETURN_VCONN_SRC (1<<6) +#define DPM_REACTION_REJECT_CANCEL (1<<7) /* FLAG ONLY */ + +#define DPM_REACTION_DFP_FLOW_DELAY (1<<10) +#define DPM_REACTION_UFP_FLOW_DELAY (1<<11) +#define DPM_REACTION_VCONN_STABLE_DELAY (1<<12) + +#define DPM_REACTION_DISCOVER_ID (1<<16) +#define DPM_REACTION_DISCOVER_SVID (1<<17) +#define DPM_REACTION_DISCOVER_CABLE (1<<18) +#define DPM_REACTION_DYNAMIC_VCONN (1<<19) + +#define DPM_REACTION_DISCOVER_CABLE_FLOW (\ + DPM_REACTION_DISCOVER_CABLE | \ + DPM_REACTION_REQUEST_VCONN_SRC |\ + DPM_REACTION_VCONN_STABLE_DELAY |\ + DPM_REACTION_DYNAMIC_VCONN) + +#define DPM_REACTION_GET_SOURCE_CAP_EXT (1<<24) + +#define DPM_REACTION_CAP_RESET_CABLE (1<<28) +#define DPM_REACTION_CAP_READY_ONCE (1<<29) +#define DPM_REACTION_CAP_DISCOVER_CABLE (1<<30) +#define DPM_REACTION_CAP_ALWAYS (1<<31) + +static inline void dpm_reaction_clear(struct pd_port *pd_port, uint32_t mask) +{ + pd_port->pe_data.dpm_ready_reactions &= ~mask; +} + +static inline void dpm_reaction_set(struct pd_port *pd_port, uint32_t mask) +{ + pd_port->pe_data.dpm_ready_reactions |= mask; +} + +static inline void dpm_reaction_set_ready_once(struct pd_port *pd_port) +{ + if (pd_check_pe_state_ready(pd_port)) + dpm_reaction_set(pd_port, DPM_REACTION_CAP_READY_ONCE); +} + +static inline void dpm_reaction_set_clear( + struct pd_port *pd_port, uint32_t set, uint32_t clear) +{ + uint32_t val = pd_port->pe_data.dpm_ready_reactions | set; + + pd_port->pe_data.dpm_ready_reactions = val & (~clear); +} + +static inline uint32_t dpm_reaction_check( + struct pd_port *pd_port, uint32_t mask) +{ + return pd_port->pe_data.dpm_ready_reactions & mask; +} +#endif /* PD_DPM_CORE_H */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/pd_dpm_pdo_select.h b/drivers/usb/typec/mmi_tcpc/inc/pd_dpm_pdo_select.h new file mode 100644 index 000000000000..818d6a71f016 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/pd_dpm_pdo_select.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PD_DPM_PDO_SELECT_H +#define PD_DPM_PDO_SELECT_H + +#include "inc/tcpci.h" + +struct dpm_pdo_info_t { + uint8_t type; + uint8_t apdo_type; + uint8_t pwr_limit; + int vmin; + int vmax; + int uw; + int ma; +}; + +struct dpm_rdo_info_t { + uint8_t pos; + uint8_t type; + bool mismatch; + + int vmin; + int vmax; + + union { + uint32_t max_uw; + uint32_t max_ma; + }; + + union { + uint32_t oper_uw; + uint32_t oper_ma; + }; +}; + +#define DPM_PDO_TYPE_FIXED TCPM_POWER_CAP_VAL_TYPE_FIXED +#define DPM_PDO_TYPE_VAR TCPM_POWER_CAP_VAL_TYPE_VARIABLE +#define DPM_PDO_TYPE_BAT TCPM_POWER_CAP_VAL_TYPE_BATTERY +#define DPM_PDO_TYPE_APDO TCPM_POWER_CAP_VAL_TYPE_AUGMENT + +#define DPM_APDO_TYPE_PPS (TCPM_POWER_CAP_APDO_TYPE_PPS) +#define DPM_APDO_TYPE_PPS_CF (TCPM_POWER_CAP_APDO_TYPE_PPS_CF) + +extern void dpm_extract_pdo_info( + uint32_t pdo, struct dpm_pdo_info_t *info); + +extern bool dpm_find_match_req_info(struct dpm_rdo_info_t *req_info, + struct dpm_pdo_info_t *sink, int cnt, uint32_t *src_pdos, + int min_uw, uint32_t select_rule); + +#endif /* PD_DPM_PDO_SELECT_H */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/pd_policy_engine.h b/drivers/usb/typec/mmi_tcpc/inc/pd_policy_engine.h new file mode 100644 index 000000000000..7d898cf6682c --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/pd_policy_engine.h @@ -0,0 +1,933 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PD_POLICY_ENGINE_H_ +#define PD_POLICY_ENGINE_H_ + +#include "pd_core.h" + +/* ---- Policy Engine State ---- */ + +enum pd_pe_state_machine { + PE_STATE_MACHINE_IDLE = 0, + PE_STATE_MACHINE_NORMAL, + PE_STATE_MACHINE_DR_SWAP, + PE_STATE_MACHINE_PR_SWAP, + PE_STATE_MACHINE_VCONN_SWAP, +}; + +/* ---- Policy Engine Runtime Flags ---- */ + +#define PE_STATE_FLAG_BACK_READY_IF_RECV_WAIT (1<<0) +#define PE_STATE_FLAG_BACK_READY_IF_RECV_REJECT (1<<1) +#define PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT (1<<2) +#define PE_STATE_FLAG_BACK_READY_IF_TX_FAILED (1<<3) +#define PE_STATE_FLAG_HRESET_IF_SR_TIMEOUT (1<<4) +#define PE_STATE_FLAG_HRESET_IF_TX_FAILED (1<<5) +#define PE_STATE_FLAG_IGNORE_UNKNOWN_EVENT (1<<6) +#define PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER (1<<7) + +#define PE_STATE_WAIT_RESPONSE(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER; } + +#define PE_STATE_WAIT_MSG(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT |\ + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER; } + +#define PE_STATE_WAIT_MSG_HRESET_IF_TOUT(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_HRESET_IF_SR_TIMEOUT |\ + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER; } + +#define PE_STATE_WAIT_MSG_OR_TX_FAILED(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_BACK_READY_IF_TX_FAILED |\ + PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT |\ + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER; } + +#define PE_STATE_WAIT_MSG_OR_RJ(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_BACK_READY_IF_RECV_REJECT |\ + PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT |\ + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER; } + +#define PE_STATE_WAIT_ANSWER_MSG(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_BACK_READY_IF_RECV_WAIT |\ + PE_STATE_FLAG_BACK_READY_IF_RECV_REJECT |\ + PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT |\ + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER; } + +#define PE_STATE_HRESET_IF_TX_FAILED(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_HRESET_IF_TX_FAILED; } + +#define PE_STATE_IGNORE_UNKNOWN_EVENT(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_IGNORE_UNKNOWN_EVENT; } + +#define PE_STATE_RECV_SOFT_RESET(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_HRESET_IF_TX_FAILED | \ + PE_STATE_FLAG_IGNORE_UNKNOWN_EVENT; } + +#define PE_STATE_SEND_SOFT_RESET(pd_port) {\ + pd_port->pe_data.pe_state_flags = \ + PE_STATE_FLAG_HRESET_IF_TX_FAILED |\ + PE_STATE_FLAG_HRESET_IF_SR_TIMEOUT |\ + PE_STATE_FLAG_IGNORE_UNKNOWN_EVENT |\ + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER; } + +#define PE_STATE_FLAG_BACK_READY_IF_RECV_GOOD_CRC (1<<0) +#define PE_STATE_FLAG_BACK_READY_IF_DPM_ACK (1<<1) +#define PE_STATE_FLAG_DPM_ACK_IMMEDIATELY (1<<7) + +#define PE_STATE_WAIT_TX_SUCCESS(pd_port) {\ + pd_port->pe_data.pe_state_flags2 = \ + PE_STATE_FLAG_BACK_READY_IF_RECV_GOOD_CRC; } + +#define PE_STATE_DPM_INFORMED(pd_port) {\ + pd_port->pe_data.pe_state_flags2 = \ + PE_STATE_FLAG_BACK_READY_IF_DPM_ACK |\ + PE_STATE_FLAG_DPM_ACK_IMMEDIATELY; } + +#define PE_STATE_WAIT_DPM_ACK(pd_port) {\ + pd_port->pe_data.pe_state_flags2 = \ + PE_STATE_FLAG_BACK_READY_IF_DPM_ACK; } + +#define PE_STATE_DPM_ACK_IMMEDIATELY(pd_port) {\ + pd_port->pe_data.pe_state_flags2 |= \ + PE_STATE_FLAG_DPM_ACK_IMMEDIATELY; } + +#define VDM_STATE_FLAG_ENABLE_VDM_RESPONSE_TIMER (1<<0) +#define VDM_STATE_FLAG_DPM_ACK_IMMEDIATELY (1<<4) +#define VDM_STATE_FLAG_BACK_READY_IF_DPM_ACK (1<<6) +#define VDM_STATE_FLAG_BACK_READY_IF_RECV_GOOD_CRC (1<<7) + +#define VDM_STATE_DPM_INFORMED(pd_port) {\ + pd_port->pe_data.vdm_state_flags = \ + VDM_STATE_FLAG_BACK_READY_IF_DPM_ACK |\ + VDM_STATE_FLAG_DPM_ACK_IMMEDIATELY; } + +#define VDM_STATE_REPLY_SVDM_REQUEST(pd_port) {\ + pd_port->pe_data.vdm_state_flags = \ + VDM_STATE_FLAG_BACK_READY_IF_RECV_GOOD_CRC; } + +#define VDM_STATE_NORESP_CMD(pd_port) {\ + pd_port->pe_data.vdm_state_flags = \ + VDM_STATE_FLAG_BACK_READY_IF_RECV_GOOD_CRC; } + +#define VDM_STATE_RESPONSE_CMD(pd_port, timer_id) {\ + pd_port->pe_data.vdm_state_flags = \ + VDM_STATE_FLAG_ENABLE_VDM_RESPONSE_TIMER; \ + pd_port->pe_data.vdm_state_timer = timer_id; } + +static inline bool pd_check_pe_during_hard_reset(struct pd_port *pd_port) +{ + return pd_port->tcpc->pd_wait_hard_reset_complete; +} + +enum pd_pe_state { + PE_STATE_START_ID = -1, +/******************* Source *******************/ +#ifdef CONFIG_USB_PD_PE_SOURCE + PE_SRC_STARTUP, + PE_SRC_DISCOVERY, + PE_SRC_SEND_CAPABILITIES, + PE_SRC_NEGOTIATE_CAPABILITIES, + PE_SRC_TRANSITION_SUPPLY, + PE_SRC_TRANSITION_SUPPLY2, + PE_SRC_READY, + PE_SRC_DISABLED, + PE_SRC_CAPABILITY_RESPONSE, + PE_SRC_HARD_RESET, + PE_SRC_HARD_RESET_RECEIVED, + PE_SRC_TRANSITION_TO_DEFAULT, + PE_SRC_GET_SINK_CAP, + PE_SRC_WAIT_NEW_CAPABILITIES, + PE_SRC_SEND_SOFT_RESET, + PE_SRC_SOFT_RESET, + +/* Source Startup Discover Cable */ +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#ifdef CONFIG_PD_SRC_RESET_CABLE + PE_SRC_CBL_SEND_SOFT_RESET, +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + PE_SRC_VDM_IDENTITY_REQUEST, + PE_SRC_VDM_IDENTITY_ACKED, + PE_SRC_VDM_IDENTITY_NAKED, +#endif /* PD_CAP_PE_SRC_STARTUP_DISCOVER_ID */ + +/* Source for PD30 */ +#ifdef CONFIG_USB_PD_REV30 + PE_SRC_SEND_NOT_SUPPORTED, + PE_SRC_NOT_SUPPORTED_RECEIVED, + PE_SRC_CHUNK_RECEIVED, +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + PE_SRC_SEND_SOURCE_ALERT, +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + PE_SRC_SINK_ALERT_RECEIVED, +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + PE_SRC_GIVE_SOURCE_CAP_EXT, +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + PE_SRC_GIVE_SOURCE_STATUS, +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + PE_SRC_GET_SINK_STATUS, +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + PE_SRC_GIVE_PPS_STATUS, +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SOURCE */ + +/******************* Sink *******************/ +#ifdef CONFIG_USB_PD_PE_SINK +/* Sink Init */ + PE_SNK_STARTUP, + PE_SNK_DISCOVERY, + PE_SNK_WAIT_FOR_CAPABILITIES, + PE_SNK_EVALUATE_CAPABILITY, + PE_SNK_SELECT_CAPABILITY, + PE_SNK_TRANSITION_SINK, + PE_SNK_READY, + PE_SNK_HARD_RESET, + PE_SNK_TRANSITION_TO_DEFAULT, + PE_SNK_GIVE_SINK_CAP, + PE_SNK_GET_SOURCE_CAP, + + PE_SNK_SEND_SOFT_RESET, + PE_SNK_SOFT_RESET, + +/* Sink for PD30 */ +#ifdef CONFIG_USB_PD_REV30 + PE_SNK_SEND_NOT_SUPPORTED, + PE_SNK_NOT_SUPPORTED_RECEIVED, + PE_SNK_CHUNK_RECEIVED, +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + PE_SNK_SOURCE_ALERT_RECEIVED, +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + PE_SNK_SEND_SINK_ALERT, +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + PE_SNK_GET_SOURCE_CAP_EXT, +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + PE_SNK_GET_SOURCE_STATUS, +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + PE_SNK_GIVE_SINK_STATUS, +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + PE_SNK_GET_PPS_STATUS, +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SINK */ + +/******************* DR_SWAP *******************/ +#ifdef CONFIG_USB_PD_DR_SWAP +/* DR_SWAP_DFP */ + PE_DRS_DFP_UFP_EVALUATE_DR_SWAP, + PE_DRS_DFP_UFP_ACCEPT_DR_SWAP, + PE_DRS_DFP_UFP_CHANGE_TO_UFP, + PE_DRS_DFP_UFP_SEND_DR_SWAP, + PE_DRS_DFP_UFP_REJECT_DR_SWAP, +/* DR_SWAP_UFP */ + PE_DRS_UFP_DFP_EVALUATE_DR_SWAP, + PE_DRS_UFP_DFP_ACCEPT_DR_SWAP, + PE_DRS_UFP_DFP_CHANGE_TO_DFP, + PE_DRS_UFP_DFP_SEND_DR_SWAP, + PE_DRS_UFP_DFP_REJECT_DR_SWAP, +#endif /* CONFIG_USB_PD_DR_SWAP */ + +/******************* PR_SWAP *******************/ +#ifdef CONFIG_USB_PD_PR_SWAP +/* PR_SWAP_SRC */ + PE_PRS_SRC_SNK_EVALUATE_PR_SWAP, + PE_PRS_SRC_SNK_ACCEPT_PR_SWAP, + PE_PRS_SRC_SNK_TRANSITION_TO_OFF, + PE_PRS_SRC_SNK_ASSERT_RD, + PE_PRS_SRC_SNK_WAIT_SOURCE_ON, + PE_PRS_SRC_SNK_SEND_SWAP, + PE_PRS_SRC_SNK_REJECT_PR_SWAP, + +/* PR_SWAP_SNK */ + PE_PRS_SNK_SRC_EVALUATE_PR_SWAP, + PE_PRS_SNK_SRC_ACCEPT_PR_SWAP, + PE_PRS_SNK_SRC_TRANSITION_TO_OFF, + PE_PRS_SNK_SRC_ASSERT_RP, + PE_PRS_SNK_SRC_SOURCE_ON, + PE_PRS_SNK_SRC_SEND_SWAP, + PE_PRS_SNK_SRC_REJECT_SWAP, + +/* get same role cap */ + PE_DR_SRC_GET_SOURCE_CAP, + PE_DR_SRC_GIVE_SINK_CAP, + PE_DR_SNK_GET_SINK_CAP, + PE_DR_SNK_GIVE_SOURCE_CAP, + +/* get same role cap for PD30 */ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + PE_DR_SNK_GIVE_SOURCE_CAP_EXT, +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + PE_DR_SRC_GET_SOURCE_CAP_EXT, +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PR_SWAP */ + +/******************* VCONN_SWAP *******************/ +#ifdef CONFIG_USB_PD_VCONN_SWAP + PE_VCS_SEND_SWAP, + PE_VCS_EVALUATE_SWAP, + PE_VCS_ACCEPT_SWAP, + PE_VCS_REJECT_VCONN_SWAP, + PE_VCS_WAIT_FOR_VCONN, + PE_VCS_TURN_OFF_VCONN, + PE_VCS_TURN_ON_VCONN, + PE_VCS_SEND_PS_RDY, +#endif /* CONFIG_USB_PD_VCONN_SWAP */ + +/******************* UFP_VDM *******************/ + PE_UFP_VDM_GET_IDENTITY, + PE_UFP_VDM_GET_SVIDS, + PE_UFP_VDM_GET_MODES, + PE_UFP_VDM_EVALUATE_MODE_ENTRY, + PE_UFP_VDM_MODE_EXIT, + PE_UFP_VDM_ATTENTION_REQUEST, + +#ifdef CONFIG_USB_PD_ALT_MODE + PE_UFP_VDM_DP_STATUS_UPDATE, + PE_UFP_VDM_DP_CONFIGURE, +#endif/* CONFIG_USB_PD_ALT_MODE */ + +/******************* DFP_VDM *******************/ + PE_DFP_UFP_VDM_IDENTITY_REQUEST, + PE_DFP_UFP_VDM_IDENTITY_ACKED, + PE_DFP_UFP_VDM_IDENTITY_NAKED, + PE_DFP_CBL_VDM_IDENTITY_REQUEST, + PE_DFP_CBL_VDM_IDENTITY_ACKED, + PE_DFP_CBL_VDM_IDENTITY_NAKED, + + PE_DFP_VDM_SVIDS_REQUEST, + PE_DFP_VDM_SVIDS_ACKED, + PE_DFP_VDM_SVIDS_NAKED, + PE_DFP_VDM_MODES_REQUEST, + PE_DFP_VDM_MODES_ACKED, + PE_DFP_VDM_MODES_NAKED, + PE_DFP_VDM_MODE_ENTRY_REQUEST, + PE_DFP_VDM_MODE_ENTRY_ACKED, + PE_DFP_VDM_MODE_ENTRY_NAKED, + PE_DFP_VDM_MODE_EXIT_REQUEST, + PE_DFP_VDM_MODE_EXIT_ACKED, + PE_DFP_VDM_ATTENTION_REQUEST, + +#ifdef CONFIG_PD_DFP_RESET_CABLE + PE_DFP_CBL_SEND_SOFT_RESET, + PE_DFP_CBL_SEND_CABLE_RESET, +#endif /* CONFIG_PD_DFP_RESET_CABLE */ + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + PE_DFP_VDM_DP_STATUS_UPDATE_REQUEST, + PE_DFP_VDM_DP_STATUS_UPDATE_ACKED, + PE_DFP_VDM_DP_STATUS_UPDATE_NAKED, + + PE_DFP_VDM_DP_CONFIGURATION_REQUEST, + PE_DFP_VDM_DP_CONFIGURATION_ACKED, + PE_DFP_VDM_DP_CONFIGURATION_NAKED, +#endif/* CONFIG_USB_PD_ALT_MODE_DFP */ + +/******************* UVDM & SVDM *******************/ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + PE_UFP_UVDM_RECV, + PE_DFP_UVDM_SEND, + PE_DFP_UVDM_ACKED, + PE_DFP_UVDM_NAKED, +#endif/* CONFIG_USB_PD_CUSTOM_VDM */ + +/******************* PD30 Common *******************/ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE + PE_GET_BATTERY_CAP, +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL + PE_GIVE_BATTERY_CAP, +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE + PE_GET_BATTERY_STATUS, +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL + PE_GIVE_BATTERY_STATUS, +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + PE_GET_MANUFACTURER_INFO, +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + PE_GIVE_MANUFACTURER_INFO, +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE + PE_GET_COUNTRY_CODES, +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL + PE_GIVE_COUNTRY_CODES, +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE + PE_GET_COUNTRY_INFO, +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL + PE_GIVE_COUNTRY_INFO, +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ + PE_VDM_NOT_SUPPORTED, +#endif /* CONFIG_USB_PD_REV30 */ + +/******************* Others *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + PE_DBG_READY, +#endif/* CONFIG_USB_PD_CUSTOM_DBGACC */ + +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER + PE_OVER_RECV_HRESET_LIMIT, +#endif/* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + + PE_REJECT, + PE_ERROR_RECOVERY, +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE + PE_ERROR_RECOVERY_ONCE, +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + PE_BIST_TEST_DATA, + PE_BIST_CARRIER_MODE_2, +/* Wait tx finished */ + PE_IDLE1, + PE_IDLE2, + PD_NR_PE_STATES, +}; + +/** + * pd_policy_engine_run + * + * Driving the PE to get next event, and take the corresponding actions. + * + * + * Returns True if a pending event is processed; + * Returns Zero to indicate there is no pending event. + * Returns Negative Value if an error occurs. + */ + +int pd_policy_engine_run(struct tcpc_device *tcpc); + + +/* ---- Policy Engine (General) ---- */ + +void pe_power_ready_entry(struct pd_port *pd_port); + +static inline void pe_send_swap_request_entry( + struct pd_port *pd_port, uint8_t msg) +{ + PE_STATE_WAIT_ANSWER_MSG(pd_port); + pd_send_sop_ctrl_msg(pd_port, msg); +} + +/******************* Source *******************/ +#ifdef CONFIG_USB_PD_PE_SOURCE +void pe_src_startup_entry( + struct pd_port *pd_port); +void pe_src_discovery_entry( + struct pd_port *pd_port); +void pe_src_send_capabilities_entry( + struct pd_port *pd_port); +void pe_src_negotiate_capabilities_entry( + struct pd_port *pd_port); +void pe_src_transition_supply_entry( + struct pd_port *pd_port); +void pe_src_transition_supply_exit( + struct pd_port *pd_port); +void pe_src_transition_supply2_entry( + struct pd_port *pd_port); +void pe_src_ready_entry( + struct pd_port *pd_port); +void pe_src_disabled_entry( + struct pd_port *pd_port); +void pe_src_capability_response_entry( + struct pd_port *pd_port); +void pe_src_hard_reset_entry( + struct pd_port *pd_port); +void pe_src_hard_reset_received_entry( + struct pd_port *pd_port); +void pe_src_transition_to_default_entry( + struct pd_port *pd_port); +void pe_src_transition_to_default_exit( + struct pd_port *pd_port); +void pe_src_get_sink_cap_entry( + struct pd_port *pd_port); +void pe_src_get_sink_cap_exit( + struct pd_port *pd_port); +void pe_src_wait_new_capabilities_entry( + struct pd_port *pd_port); +void pe_src_send_soft_reset_entry( + struct pd_port *pd_port); +void pe_src_soft_reset_entry( + struct pd_port *pd_port); + +/* Source Startup Discover Cable */ +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#ifdef CONFIG_PD_SRC_RESET_CABLE +void pe_src_cbl_send_soft_reset_entry( + struct pd_port *pd_port); +#endif /* CONFIG_PD_SRC_RESET_CABLE */ +void pe_src_vdm_identity_request_entry( + struct pd_port *pd_port); +void pe_src_vdm_identity_acked_entry( + struct pd_port *pd_port); +void pe_src_vdm_identity_naked_entry( + struct pd_port *pd_port); +#endif /* PD_CAP_PE_SRC_STARTUP_DISCOVER_ID */ + +/* Source for PD30 */ +#ifdef CONFIG_USB_PD_REV30 +void pe_src_send_not_supported_entry( + struct pd_port *pd_port); +void pe_src_not_supported_received_entry( + struct pd_port *pd_port); +void pe_src_chunk_received_entry( + struct pd_port *pd_port); +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL +void pe_src_send_source_alert_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +void pe_src_sink_alert_received_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL +void pe_src_give_source_cap_ext_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL +void pe_src_give_source_status_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE +void pe_src_get_sink_status_entry( + struct pd_port *pd_port); +void pe_src_get_sink_status_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE +void pe_src_give_pps_status_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SOURCE */ + +/******************* Sink *******************/ +#ifdef CONFIG_USB_PD_PE_SINK +/* Sink Init */ +void pe_snk_startup_entry( + struct pd_port *pd_port); +void pe_snk_discovery_entry( + struct pd_port *pd_port); +void pe_snk_wait_for_capabilities_entry( + struct pd_port *pd_port); +void pe_snk_evaluate_capability_entry( + struct pd_port *pd_port); +void pe_snk_select_capability_entry( + struct pd_port *pd_port); +void pe_snk_select_capability_exit( + struct pd_port *pd_port); +void pe_snk_transition_sink_entry( + struct pd_port *pd_port); +void pe_snk_transition_sink_exit( + struct pd_port *pd_port); +void pe_snk_ready_entry( + struct pd_port *pd_port); +void pe_snk_hard_reset_entry( + struct pd_port *pd_port); +void pe_snk_transition_to_default_entry( + struct pd_port *pd_port); +void pe_snk_give_sink_cap_entry( + struct pd_port *pd_port); +void pe_snk_get_source_cap_entry( + struct pd_port *pd_port); + +void pe_snk_send_soft_reset_entry( + struct pd_port *pd_port); +void pe_snk_soft_reset_entry( + struct pd_port *pd_port); + +/* Sink for PD30 */ +#ifdef CONFIG_USB_PD_REV30 +void pe_snk_send_not_supported_entry( + struct pd_port *pd_port); +void pe_snk_not_supported_received_entry( + struct pd_port *pd_port); +void pe_snk_chunk_received_entry( + struct pd_port *pd_port); +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +void pe_snk_source_alert_received_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL +void pe_snk_send_sink_alert_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE +void pe_snk_get_source_cap_ext_entry( + struct pd_port *pd_port); +void pe_snk_get_source_cap_ext_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE +void pe_snk_get_source_status_entry( + struct pd_port *pd_port); +void pe_snk_get_source_status_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL +void pe_snk_give_sink_status_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +void pe_snk_get_pps_status_entry( + struct pd_port *pd_port); +void pe_snk_get_pps_status_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SINK */ + +/******************* DR_SWAP *******************/ +#ifdef CONFIG_USB_PD_DR_SWAP +/* DR_SWAP_DFP */ +void pe_drs_dfp_ufp_evaluate_dr_swap_entry( + struct pd_port *pd_port); +void pe_drs_dfp_ufp_accept_dr_swap_entry( + struct pd_port *pd_port); +void pe_drs_dfp_ufp_change_to_ufp_entry( + struct pd_port *pd_port); +void pe_drs_dfp_ufp_send_dr_swap_entry( + struct pd_port *pd_port); +void pe_drs_dfp_ufp_reject_dr_swap_entry( + struct pd_port *pd_port); +/* DR_SWAP_UFP */ +void pe_drs_ufp_dfp_evaluate_dr_swap_entry( + struct pd_port *pd_port); +void pe_drs_ufp_dfp_accept_dr_swap_entry( + struct pd_port *pd_port); +void pe_drs_ufp_dfp_change_to_dfp_entry( + struct pd_port *pd_port); +void pe_drs_ufp_dfp_send_dr_swap_entry( + struct pd_port *pd_port); +void pe_drs_ufp_dfp_reject_dr_swap_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_DR_SWAP */ + +/******************* PR_SWAP *******************/ +#ifdef CONFIG_USB_PD_PR_SWAP +/* PR_SWAP_SRC */ +void pe_prs_src_snk_evaluate_pr_swap_entry( + struct pd_port *pd_port); +void pe_prs_src_snk_accept_pr_swap_entry( + struct pd_port *pd_port); +void pe_prs_src_snk_transition_to_off_entry( + struct pd_port *pd_port); +void pe_prs_src_snk_assert_rd_entry( + struct pd_port *pd_port); +void pe_prs_src_snk_wait_source_on_entry( + struct pd_port *pd_port); +void pe_prs_src_snk_wait_source_on_exit( + struct pd_port *pd_port); +void pe_prs_src_snk_send_swap_entry( + struct pd_port *pd_port); +void pe_prs_src_snk_reject_pr_swap_entry( + struct pd_port *pd_port); + +/* PR_SWAP_SNK */ +void pe_prs_snk_src_evaluate_pr_swap_entry( + struct pd_port *pd_port); +void pe_prs_snk_src_accept_pr_swap_entry( + struct pd_port *pd_port); +void pe_prs_snk_src_transition_to_off_entry( + struct pd_port *pd_port); +void pe_prs_snk_src_transition_to_off_exit( + struct pd_port *pd_port); +void pe_prs_snk_src_assert_rp_entry( + struct pd_port *pd_port); +void pe_prs_snk_src_source_on_entry( + struct pd_port *pd_port); +void pe_prs_snk_src_source_on_exit( + struct pd_port *pd_port); +void pe_prs_snk_src_send_swap_entry( + struct pd_port *pd_port); +void pe_prs_snk_src_reject_swap_entry( + struct pd_port *pd_port); + +/* get same role cap */ +void pe_dr_src_get_source_cap_entry( + struct pd_port *pd_port); +void pe_dr_src_get_source_cap_exit( + struct pd_port *pd_port); +void pe_dr_src_give_sink_cap_entry( + struct pd_port *pd_port); +void pe_dr_snk_get_sink_cap_entry( + struct pd_port *pd_port); +void pe_dr_snk_get_sink_cap_exit( + struct pd_port *pd_port); +void pe_dr_snk_give_source_cap_entry( + struct pd_port *pd_port); + +/* get same role cap for PD30 */ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL +void pe_dr_snk_give_source_cap_ext_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE +void pe_dr_src_get_source_cap_ext_entry( + struct pd_port *pd_port); +void pe_dr_src_get_source_cap_ext_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PR_SWAP */ + +/******************* VCONN_SWAP *******************/ +#ifdef CONFIG_USB_PD_VCONN_SWAP +void pe_vcs_send_swap_entry( + struct pd_port *pd_port); +void pe_vcs_evaluate_swap_entry( + struct pd_port *pd_port); +void pe_vcs_accept_swap_entry( + struct pd_port *pd_port); +void pe_vcs_reject_vconn_swap_entry( + struct pd_port *pd_port); +void pe_vcs_wait_for_vconn_entry( + struct pd_port *pd_port); +void pe_vcs_wait_for_vconn_exit( + struct pd_port *pd_port); +void pe_vcs_turn_off_vconn_entry( + struct pd_port *pd_port); +void pe_vcs_turn_on_vconn_entry( + struct pd_port *pd_port); +void pe_vcs_send_ps_rdy_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_VCONN_SWAP */ + +/******************* UFP_VDM *******************/ +void pe_ufp_vdm_get_identity_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_send_identity_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_get_identity_nak_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_get_svids_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_send_svids_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_get_svids_nak_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_get_modes_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_send_modes_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_get_modes_nak_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_evaluate_mode_entry_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_mode_entry_ack_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_mode_entry_nak_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_mode_exit_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_mode_exit_ack_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_mode_exit_nak_entry( + struct pd_port *pd_port); + +void pe_ufp_vdm_attention_request_entry( + struct pd_port *pd_port); + +#ifdef CONFIG_USB_PD_ALT_MODE +void pe_ufp_vdm_dp_status_update_entry( + struct pd_port *pd_port); +void pe_ufp_vdm_dp_configure_entry( + struct pd_port *pd_port); +#endif/* CONFIG_USB_PD_ALT_MODE */ +/******************* DFP_VDM *******************/ +void pe_dfp_ufp_vdm_identity_request_entry( + struct pd_port *pd_port); +void pe_dfp_ufp_vdm_identity_acked_entry( + struct pd_port *pd_port); +void pe_dfp_ufp_vdm_identity_naked_entry( + struct pd_port *pd_port); +void pe_dfp_cbl_vdm_identity_request_entry( + struct pd_port *pd_port); +void pe_dfp_cbl_vdm_identity_acked_entry( + struct pd_port *pd_port); +void pe_dfp_cbl_vdm_identity_naked_entry( + struct pd_port *pd_port); + +void pe_dfp_vdm_svids_request_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_svids_acked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_svids_naked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_modes_request_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_modes_acked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_modes_naked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_mode_entry_request_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_mode_entry_acked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_mode_entry_naked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_mode_exit_request_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_mode_exit_acked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_attention_request_entry( + struct pd_port *pd_port); + +#ifdef CONFIG_PD_DFP_RESET_CABLE +void pe_dfp_cbl_send_soft_reset_entry( + struct pd_port *pd_port); +void pe_dfp_cbl_send_cable_reset_entry( + struct pd_port *pd_port); +#endif /* CONFIG_PD_DFP_RESET_CABLE */ +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +void pe_dfp_vdm_dp_status_update_request_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_dp_status_update_acked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_dp_status_update_naked_entry( + struct pd_port *pd_port); + +void pe_dfp_vdm_dp_configuration_request_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_dp_configuration_acked_entry( + struct pd_port *pd_port); +void pe_dfp_vdm_dp_configuration_naked_entry( + struct pd_port *pd_port); +#endif/* CONFIG_USB_PD_ALT_MODE_DFP */ +/******************* UVDM & SVDM *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_VDM +void pe_ufp_uvdm_recv_entry( + struct pd_port *pd_port); +void pe_dfp_uvdm_send_entry( + struct pd_port *pd_port); +void pe_dfp_uvdm_acked_entry( + struct pd_port *pd_port); +void pe_dfp_uvdm_naked_entry( + struct pd_port *pd_port); +#endif/* CONFIG_USB_PD_CUSTOM_VDM */ + +/******************* PD30 Common *******************/ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE +void pe_get_battery_cap_entry( + struct pd_port *pd_port); +void pe_get_battery_cap_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL +void pe_give_battery_cap_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE +void pe_get_battery_status_entry( + struct pd_port *pd_port); +void pe_get_battery_status_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL +void pe_give_battery_status_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE +void pe_get_manufacturer_info_entry( + struct pd_port *pd_port); +void pe_get_manufacturer_info_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL +void pe_give_manufacturer_info_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE +void pe_get_country_codes_entry( + struct pd_port *pd_port); +void pe_get_country_codes_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL +void pe_give_country_codes_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE +void pe_get_country_info_entry( + struct pd_port *pd_port); +void pe_get_country_info_exit( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL +void pe_give_country_info_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ +void pe_vdm_not_supported_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_REV30 */ +/******************* Others *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC +void pe_dbg_ready_entry( + struct pd_port *pd_port); +#endif/* CONFIG_USB_PD_CUSTOM_DBGACC */ + +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER +void pe_over_recv_hreset_limit_entry( + struct pd_port *pd_port); +#endif/* CONFIG_USB_PD_RECV_HRESET_COUNTER */ +void pe_reject_entry( + struct pd_port *pd_port); +void pe_error_recovery_entry( + struct pd_port *pd_port); +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE +void pe_error_recovery_once_entry( + struct pd_port *pd_port); +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ +void pe_bist_test_data_entry( + struct pd_port *pd_port); +void pe_bist_test_data_exit( + struct pd_port *pd_port); +void pe_bist_carrier_mode_2_entry( + struct pd_port *pd_port); +void pe_bist_carrier_mode_2_exit( + struct pd_port *pd_port); +/* Wait tx finished */ +void pe_idle1_entry( + struct pd_port *pd_port); +void pe_idle2_entry( + struct pd_port *pd_port); + +#endif /* PD_POLICY_ENGINE_H_ */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/pd_process_evt.h b/drivers/usb/typec/mmi_tcpc/inc/pd_process_evt.h new file mode 100644 index 000000000000..52aac796f05c --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/pd_process_evt.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PD_PROCESS_EVT_H_ +#define PD_PROCESS_EVT_H_ + +#include "tcpci.h" +#include "pd_policy_engine.h" + +/*---------------------------------------------------------------------------*/ + +struct pe_state_transition { + uint8_t curr_state; /*state, msg, or cmd */ + uint8_t next_state; +}; + +struct pe_state_reaction { + uint16_t nr_transition; + const struct pe_state_transition *state_transition; +}; + +#define DECL_PE_STATE_TRANSITION(state) \ + static const struct pe_state_transition state##_state_transition[] + +#define DECL_PE_STATE_REACTION(state) \ + static const struct pe_state_reaction state##_reactions = {\ + .nr_transition = ARRAY_SIZE(state##_state_transition),\ + .state_transition = state##_state_transition,\ + } + +/*----------------------------------------------------------------------------- + * Sink & Source Common Event + *--------------------------------------------------------------------------- + */ + +bool pd_process_protocol_error( + struct pd_port *pd_port, struct pd_event *pd_event); + +bool pd_process_tx_failed(struct pd_port *pd_port); + +/*---------------------------------------------------------------------------*/ + +#define PE_TRANSIT_STATE(pd_port, state) \ + (pd_port->pe_state_next = state) + +#define PE_TRANSIT_DATA_STATE(pd_port, ufp, dfp) \ + (pd_port->pe_state_next =\ + ((pd_port->data_role == PD_ROLE_UFP) ? ufp : dfp)) + +static inline uint8_t pe_get_curr_ready_state(struct pd_port *pd_port) +{ + return pd_port->curr_ready_state; +} + +static inline uint8_t pe_get_curr_hard_reset_state(struct pd_port *pd_port) +{ + return pd_port->curr_hreset_state; +} + +static inline uint8_t pe_get_curr_soft_reset_state(struct pd_port *pd_port) +{ + return pd_port->curr_sreset_state; +} + +static inline uint8_t pe_get_curr_evaluate_pr_swap_state( + struct pd_port *pd_port) +{ + if (pd_port->power_role == PD_ROLE_SINK) + return PE_PRS_SNK_SRC_EVALUATE_PR_SWAP; + + return PE_PRS_SRC_SNK_EVALUATE_PR_SWAP; +} + +static inline uint8_t pe_get_curr_send_pr_swap_state( + struct pd_port *pd_port) +{ + if (pd_port->power_role == PD_ROLE_SINK) + return PE_PRS_SNK_SRC_SEND_SWAP; + + return PE_PRS_SRC_SNK_SEND_SWAP; +} + +static inline uint8_t pd_get_curr_hard_reset_recv_state( + struct pd_port *pd_port) +{ + if (pd_port->power_role == PD_ROLE_SINK) + return PE_SNK_TRANSITION_TO_DEFAULT; + + return PE_SRC_HARD_RESET_RECEIVED; +} + +static inline uint8_t pd_get_curr_soft_reset_recv_state( + struct pd_port *pd_port) +{ + if (pd_port->power_role == PD_ROLE_SINK) + return PE_SNK_SOFT_RESET; + + return PE_SRC_SOFT_RESET; +} + +static inline void pe_transit_ready_state(struct pd_port *pd_port) +{ + PE_TRANSIT_STATE(pd_port, pe_get_curr_ready_state(pd_port)); +} + +static inline void pe_transit_hard_reset_state(struct pd_port *pd_port) +{ + PE_TRANSIT_STATE(pd_port, pe_get_curr_hard_reset_state(pd_port)); +} + +static inline void pe_transit_soft_reset_state(struct pd_port *pd_port) +{ + PE_TRANSIT_STATE(pd_port, pe_get_curr_soft_reset_state(pd_port)); +} + +static inline void pe_transit_soft_reset_recv_state(struct pd_port *pd_port) +{ + PE_TRANSIT_STATE(pd_port, pd_get_curr_soft_reset_recv_state(pd_port)); +} + +static inline void pe_transit_evaluate_pr_swap_state(struct pd_port *pd_port) +{ + PE_TRANSIT_STATE(pd_port, + pe_get_curr_evaluate_pr_swap_state(pd_port)); +} + +static inline void pe_transit_send_pr_swap_state(struct pd_port *pd_port) +{ + PE_TRANSIT_STATE(pd_port, + pe_get_curr_send_pr_swap_state(pd_port)); +} + +static inline void pe_transit_hard_reset_recv_state(struct pd_port *pd_port) +{ + PE_TRANSIT_STATE(pd_port, + pd_get_curr_hard_reset_recv_state(pd_port)); +} + +/*---------------------------------------------------------------------------*/ + +static inline bool pd_check_pe_state_ready(struct pd_port *pd_port) +{ + uint8_t ready_state = pe_get_curr_ready_state(pd_port); + + return pd_port->pe_state_curr == ready_state; +} + +/*---------------------------------------------------------------------------*/ + +#define PE_MAKE_STATE_TRANSIT_SINGLE(reaction, next) \ + pd_make_pe_state_transit_single(\ + pd_port, pd_port->pe_state_curr, reaction, next) +/* PE_MAKE_STATE_TRANSIT_SINGLE */ + +#define PE_MAKE_STATE_TRANSIT_TO_HRESET(reaction) \ + PE_MAKE_STATE_TRANSIT_SINGLE(reaction, \ + pe_get_curr_hard_reset_state(pd_port)) +/* PE_MAKE_STATE_TRANSIT_TO_HRESET */ + +#define PE_MAKE_STATE_TRANSIT(state) \ + pd_make_pe_state_transit(\ + pd_port, pd_port->pe_state_curr, &state##_reactions) +/* PE_MAKE_STATE_TRANSIT */ + + +static inline bool pd_make_pe_state_transit_single(struct pd_port *pd_port, + uint8_t curr_state, uint8_t reaction_state, uint8_t next_state) +{ + if (curr_state == reaction_state) { + PE_TRANSIT_STATE(pd_port, next_state); + return true; + } + + return false; +} + +bool pd_make_pe_state_transit(struct pd_port *pd_port, uint8_t curr_state, + const struct pe_state_reaction *state_reaction); + +bool pd_process_event(struct pd_port *pd_port, struct pd_event *pd_event); + +extern bool pd_process_event_snk(struct pd_port *pd_port, struct pd_event *evt); +extern bool pd_process_event_src(struct pd_port *pd_port, struct pd_event *evt); +extern bool pd_process_event_drs(struct pd_port *pd_port, struct pd_event *evt); +extern bool pd_process_event_prs(struct pd_port *pd_port, struct pd_event *evt); +extern bool pd_process_event_vdm(struct pd_port *pd_port, struct pd_event *evt); +extern bool pd_process_event_vcs(struct pd_port *pd_port, struct pd_event *evt); +extern bool pd_process_event_com(struct pd_port *pd_port, struct pd_event *evt); +extern bool pd_process_event_tcp(struct pd_port *pd_port, struct pd_event *evt); + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC +extern bool pd_process_event_dbg(struct pd_port *pd_port, struct pd_event *evt); +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + +#endif /* PD_PROCESS_EVT_H_ */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/rt-regmap.h b/drivers/usb/typec/mmi_tcpc/inc/rt-regmap.h new file mode 100644 index 000000000000..abe06e34abfb --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/rt-regmap.h @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 MediaTek Inc. + * Author: cy_huang + */ + +#ifndef LINUX_MISC_RT_REGMAP_H +#define LINUX_MISC_RT_REGMAP_H + +#include + +enum rt_access_mode { + RT_1BYTE_MODE = 1, + RT_2BYTE_MODE = 2, + RT_4BYTE_MODE = 4, + RT_DUMMY_MODE = -1, +}; + +/* start: the start address of group + * end: the end address of group + * mode: access mode (1,2,4 bytes) + */ +struct rt_access_group { + u32 start; + u32 end; + enum rt_access_mode mode; +}; + +/* rt_reg_type + * RT_NORMAL : Write without mask, Read from cache + * RT_WBITS : Write with mask, Read from cache + * RT_VOLATILE : Write to chip directly, Read from chip + * RT_RESERVE : Reserve registers (Write/Read as RT_NORMAL) + */ +#define RT_REG_TYPE_MASK (0x03) +#define RT_NORMAL (0x00) +#define RT_WBITS (0x01) +#define RT_VOLATILE (0x02) +#define RT_RESERVE (0x03) + +/* RT_WR_ONCE: if write data = cache data, data will not be written. + */ +#define RT_WR_ONCE (0x08) +#define RT_NORMAL_WR_ONCE (RT_NORMAL | RT_WR_ONCE) +#define RT_WBITS_WR_ONCE (RT_WBITS | RT_WR_ONCE) + +enum rt_data_format { + RT_LITTLE_ENDIAN, + RT_BIG_ENDIAN, +}; + +/* rt_regmap_mode + * 0 0 0 0 0 0 0 0 + * | | | | | | + * | | | |__| byte_mode + * | |__| || + * | || Cache_mode + * | Block_mode + * Debug_mode + */ + +#define RT_BYTE_MODE_MASK (0x01) +/* 1 byte for each register */ +#define RT_SINGLE_BYTE (0 << 0) +/* multi bytes for each regiseter */ +#define RT_MULTI_BYTE (1 << 0) + +#define RT_CACHE_MODE_MASK (0x06) +/* write to cache and chip synchronously */ +#define RT_CACHE_WR_THROUGH (0 << 1) +/* write to cache and chip asynchronously */ +#define RT_CACHE_WR_BACK (1 << 1) +/* disable cache */ +#define RT_CACHE_DISABLE (2 << 1) + +#define RT_IO_BLK_MODE_MASK (0x18) +/* pass through all write function */ +#define RT_IO_PASS_THROUGH (0 << 3) +/* block all write function */ +#define RT_IO_BLK_ALL (1 << 3) +/* block cache write function */ +#define RT_IO_BLK_CACHE (2 << 3) +/* block chip write function */ +#define RT_IO_BLK_CHIP (3 << 3) + +#define RT_DBG_MODE_MASK (0x20) +/* create general debugfs for register map */ +#define RT_DBG_GENERAL (0 << 5) +/* create node for each regisetr map by register address */ +#define RT_DBG_SPECIAL (1 << 5) + + +/* struct rt_register + * + * Ricktek register map structure for storing mapping data + * @addr: register address. + * @size: register size in byte. + * @reg_type: register R/W type (RT_NORMAL, RT_WBITS, RT_VOLATILE, RT_RESERVE). + * @wbit_mask: register writeable bits mask. + */ +struct rt_register { + u32 addr; + unsigned int size; + unsigned char reg_type; + unsigned char *wbit_mask; +}; + +/* Declare a rt_register by RT_REG_DECL + */ +#define RT_REG_DECL(_addr, _reg_length, _reg_type, _mask_...) \ + static unsigned char rt_writable_mask_##_addr[_reg_length] = _mask_; \ + static struct rt_register rt_register_##_addr = { \ + .addr = _addr, \ + .size = _reg_length, \ + .reg_type = _reg_type, \ + .wbit_mask = rt_writable_mask_##_addr, \ + } + +#define rt_register_map_t struct rt_register * + +#define RT_REG(_addr) (&rt_register_##_addr) + +/* rt_regmap_properties + * @name: the name of debug node. + * @aliases: alisis name of rt_regmap_device. + * @rm: rt_regiseter_map pointer array. + * @register_num: the number of rt_register_map registers. + * @group: register map access group. + * @rt_format: default is little endian. + * @rt_regmap_mode: rt_regmap_device mode. + * @io_log_en: enable/disable io log + */ +struct rt_regmap_properties { + const char *name; + const char *aliases; + const rt_register_map_t *rm; + int register_num; + struct rt_access_group *group; + const enum rt_data_format rt_format; + unsigned char rt_regmap_mode; + unsigned char io_log_en:1; +}; + +/* A passing struct for rt_regmap_reg_read and rt_regmap_reg_write function + * reg: regmap addr. + * mask: mask for update bits. + * rt_data: register value. + */ +struct rt_reg_data { + u32 reg; + u32 mask; + union { + u32 data_u32; + u16 data_u16; + u8 data_u8; + u8 data[4]; + } rt_data; +}; + +struct rt_regmap_device; + +/* basic chip read/write function */ +struct rt_regmap_fops { + int (*read_device)(void *client, u32 addr, int len, void *dst); + int (*write_device)(void *client, u32 addr, int len, const void *src); +}; + +/* with device address */ +extern struct rt_regmap_device* +rt_regmap_device_register_ex(struct rt_regmap_properties *props, + struct rt_regmap_fops *rops, + struct device *parent, + void *client, int dev_addr, void *drvdata); + +static inline struct rt_regmap_device* +rt_regmap_device_register(struct rt_regmap_properties *props, + struct rt_regmap_fops *rops, + struct device *parent, + struct i2c_client *client, void *drvdata) +{ + return rt_regmap_device_register_ex(props, rops, parent, + client, client->addr, drvdata); +} + +extern void rt_regmap_device_unregister(struct rt_regmap_device *rd); + +extern int rt_regmap_cache_reload(struct rt_regmap_device *rd); + +extern int rt_regmap_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src); +extern int rt_asyn_regmap_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src); +extern int rt_regmap_block_read(struct rt_regmap_device *rd, u32 reg, + int bytes, void *dst); +extern int rt_regmap_reg_read(struct rt_regmap_device *rd, + struct rt_reg_data *rrd, u32 reg); +extern int rt_regmap_reg_write(struct rt_regmap_device *rd, + struct rt_reg_data *rrd, u32 reg, + const u32 data); +extern int rt_asyn_regmap_reg_write(struct rt_regmap_device *rd, + struct rt_reg_data *rrd, u32 reg, + const u32 data); +extern int rt_regmap_update_bits(struct rt_regmap_device *rd, + struct rt_reg_data *rrd, u32 reg, + u32 mask, u32 data); +extern void rt_regmap_cache_sync(struct rt_regmap_device *rd); +extern void rt_regmap_cache_write_back(struct rt_regmap_device *rd, u32 reg); +extern int rt_is_reg_volatile(struct rt_regmap_device *rd, u32 reg); +extern int rt_get_regsize(struct rt_regmap_device *rd, u32 reg); +extern void rt_cache_getlasterror(struct rt_regmap_device *rd, char *buf, + size_t size); +extern void rt_cache_clrlasterror(struct rt_regmap_device *rd); +extern int rt_regmap_add_debugfs(struct rt_regmap_device *rd, const char *name, + umode_t mode, void *data, + const struct file_operations *fops); +extern int regmap_plat_init(void); +extern void regmap_plat_exit(void); +#endif /* LINUX_MISC_RT_REGMAP_H */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/rt1711h.h b/drivers/usb/typec/mmi_tcpc/inc/rt1711h.h new file mode 100644 index 000000000000..c710ef19c96a --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/rt1711h.h @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_RT1711H_H +#define __LINUX_RT1711H_H + +#include "std_tcpci_v10.h" +#include "pd_dbg_info.h" + +/*show debug message or not */ +#define ENABLE_RT1711_DBG 0 + +/* RT1711H Private RegMap */ + +#define RT1711H_REG_CONFIG_GPIO0 (0x71) + +#define RT1711H_REG_PHY_CTRL1 (0x80) + +#define RT1711H_REG_CLK_CTRL2 (0x87) +#define RT1711H_REG_CLK_CTRL3 (0x88) + +#define RT1711H_REG_PRL_FSM_RESET (0x8D) + +#define RT1711H_REG_BMC_CTRL (0x90) +#define RT1711H_REG_BMCIO_RXDZSEL (0x93) +#define RT1711H_REG_VCONN_CLIMITEN (0x95) + +#define RT1711H_REG_RT_STATUS (0x97) +#define RT1711H_REG_RT_INT (0x98) +#define RT1711H_REG_RT_MASK (0x99) + +#define RT1711H_REG_IDLE_CTRL (0x9B) +#define RT1711H_REG_INTRST_CTRL (0x9C) +#define RT1711H_REG_WATCHDOG_CTRL (0x9D) +#define RT1711H_REG_I2CRST_CTRL (0X9E) + +#define RT1711H_REG_SWRESET (0xA0) +#define RT1711H_REG_TTCPC_FILTER (0xA1) +#define RT1711H_REG_DRP_TOGGLE_CYCLE (0xA2) +#define RT1711H_REG_DRP_DUTY_CTRL (0xA3) +#define RT1711H_REG_BMCIO_RXDZEN (0xAF) + +#define RT1711H_REG_UNLOCK_PW_2 (0xF0) +#define RT1711H_REG_UNLOCK_PW_1 (0xF1) +#define RT1711H_REG_EFUSE5 (0xF6) + +/* + * Device ID + */ + +#define RT1711H_DID_A 0x2170 +#define RT1711H_DID_B 0x2171 +#define RT1711H_DID_C 0x2172 + +#define RT1715_DID_D 0x2173 + +/* + * RT1711H_REG_PHY_CTRL1 (0x80) + */ + +#define RT1711H_REG_PHY_CTRL1_SET( \ + retry_discard, toggle_cnt, bus_idle_cnt, rx_filter) \ + ((retry_discard << 7) | (toggle_cnt << 4) | \ + (bus_idle_cnt << 2) | (rx_filter & 0x03)) + +/* + * RT1711H_REG_CLK_CTRL2 (0x87) + */ + +#define RT1711H_REG_CLK_DIV_600K_EN (1<<7) +#define RT1711H_REG_CLK_BCLK2_EN (1<<6) +#define RT1711H_REG_CLK_BCLK2_TG_EN (1<<5) +#define RT1711H_REG_CLK_DIV_300K_EN (1<<3) +#define RT1711H_REG_CLK_CK_300K_EN (1<<2) +#define RT1711H_REG_CLK_BCLK_EN (1<<1) +#define RT1711H_REG_CLK_BCLK_TH_EN (1<<0) + +/* + * RT1711H_REG_CLK_CTRL3 (0x88) + */ + +#define RT1711H_REG_CLK_OSCMUX_RG_EN (1<<7) +#define RT1711H_REG_CLK_CK_24M_EN (1<<6) +#define RT1711H_REG_CLK_OSC_RG_EN (1<<5) +#define RT1711H_REG_CLK_DIV_2P4M_EN (1<<4) +#define RT1711H_REG_CLK_CK_2P4M_EN (1<<3) +#define RT1711H_REG_CLK_PCLK_EN (1<<2) +#define RT1711H_REG_CLK_PCLK_RG_EN (1<<1) +#define RT1711H_REG_CLK_PCLK_TG_EN (1<<0) + +/* + * RT1711H_REG_BMC_CTRL (0x90) + */ + +#define RT1711H_REG_IDLE_EN (1<<6) +#define RT1711H_REG_DISCHARGE_EN (1<<5) +#define RT1711H_REG_BMCIO_LPRPRD (1<<4) +#define RT1711H_REG_BMCIO_LPEN (1<<3) +#define RT1711H_REG_BMCIO_BG_EN (1<<2) +#define RT1711H_REG_VBUS_DET_EN (1<<1) +#define RT1711H_REG_BMCIO_OSC_EN (1<<0) + +/* + * RT1711H_REG_RT_STATUS (0x97) + */ + +#define RT1711H_REG_RA_DETACH (1<<5) +#define RT1711H_REG_VBUS_80 (1<<1) + +/* + * RT1711H_REG_RT_INT (0x98) + */ + +#define RT1711H_REG_INT_RA_DETACH (1<<5) +#define RT1711H_REG_INT_WATCHDOG (1<<2) +#define RT1711H_REG_INT_VBUS_80 (1<<1) +#define RT1711H_REG_INT_WAKEUP (1<<0) + +/* + * RT1711H_REG_RT_MASK (0x99) + */ + +#define RT1711H_REG_M_RA_DETACH (1<<5) +#define RT1711H_REG_M_WATCHDOG (1<<2) +#define RT1711H_REG_M_VBUS_80 (1<<1) +#define RT1711H_REG_M_WAKEUP (1<<0) + +/* + * RT1711H_REG_IDLE_CTRL (0x9B) + */ + +#define RT1711H_REG_CK_300K_SEL (1<<7) +#define RT1711H_REG_SHIPPING_OFF (1<<5) +#define RT1711H_REG_ENEXTMSG (1<<4) +#define RT1711H_REG_AUTOIDLE_EN (1<<3) + +/* + * RT1711H_REG_EFUSE5 (0xF6) + */ + +#define RT1711H_REG_M_VBUS_CAL GENMASK(7, 5) +#define RT1711H_REG_S_VBUS_CAL 5 +#define RT1711H_REG_MIN_VBUS_CAL -4 + +/* timeout = (tout*2+1) * 6.4ms */ + +#ifdef CONFIG_USB_PD_REV30 +#define RT1711H_REG_IDLE_SET(ck300, ship_dis, auto_idle, tout) \ + ((ck300 << 7) | (ship_dis << 5) | (auto_idle << 3) \ + | (tout & 0x07) | RT1711H_REG_ENEXTMSG) +#else +#define RT1711H_REG_IDLE_SET(ck300, ship_dis, auto_idle, tout) \ + ((ck300 << 7) | (ship_dis << 5) | (auto_idle << 3) | (tout & 0x07)) +#endif + +/* + * RT1711H_REG_INTRST_CTRL (0x9C) + */ + +#define RT1711H_REG_INTRST_EN (1<<7) + +/* timeout = (tout+1) * 0.2sec */ +#define RT1711H_REG_INTRST_SET(en, tout) \ + ((en << 7) | (tout & 0x03)) + +/* + * RT1711H_REG_WATCHDOG_CTRL (0x9D) + */ + +#define RT1711H_REG_WATCHDOG_EN (1<<7) + +/* timeout = (tout+1) * 0.4sec */ +#define RT1711H_REG_WATCHDOG_CTRL_SET(en, tout) \ + ((en << 7) | (tout & 0x07)) + +/* + * RT1711H_REG_I2CRST_CTRL (0x9E) + */ + +#define RT1711H_REG_I2CRST_EN (1<<7) + +/* timeout = (tout+1) * 12.5ms */ +#define RT1711H_REG_I2CRST_SET(en, tout) \ + ((en << 7) | (tout & 0x0f)) + +#if ENABLE_RT1711_DBG +#define RT1711_INFO(format, args...) \ + pd_dbg_info("%s() line-%d: " format,\ + __func__, __LINE__, ##args) +#else +#define RT1711_INFO(foramt, args...) +#endif + +#endif /* #ifndef __LINUX_RT1711H_H */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/std_tcpci_v10.h b/drivers/usb/typec/mmi_tcpc/inc/std_tcpci_v10.h new file mode 100644 index 000000000000..f56b56f9c381 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/std_tcpci_v10.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef STD_TCPCI_V10_H_ +#define STD_TCPCI_V10_H_ + +/* Standard TCPC V10 RegMap */ + +#define TCPC_V10_REG_VID (0x00) +#define TCPC_V10_REG_PID (0x02) +#define TCPC_V10_REG_DID (0x04) +#define TCPC_V10_REG_TYPEC_REV (0x06) +#define TCPC_V10_REG_PD_REV (0x08) +#define TCPC_V10_REG_PDIF_REV (0x0A) + +#define TCPC_V10_REG_ALERT (0x10) +#define TCPC_V10_REG_ALERT_MASK (0x12) +#define TCPC_V10_REG_POWER_STATUS_MASK (0x14) +#define TCPC_V10_REG_FAULT_STATUS_MASK (0x15) +#define TCPC_V10_REG_EXT_STATUS_MASK (0x16) + +#define TCPC_V10_REG_TCPC_CTRL (0x19) +#define TCPC_V10_REG_ROLE_CTRL (0x1A) +#define TCPC_V10_REG_FAULT_CTRL (0x1B) +#define TCPC_V10_REG_POWER_CTRL (0x1C) + +#define TCPC_V10_REG_CC_STATUS (0x1D) +#define TCPC_V10_REG_POWER_STATUS (0x1E) +#define TCPC_V10_REG_FAULT_STATUS (0x1F) +#define TCPC_V10_REG_EXT_STATUS (0x20) + +#define TCPC_V10_REG_COMMAND (0x23) + +#define TCPC_V10_REG_MSG_HDR_INFO (0x2e) + +#define TCPC_V10_REG_RX_DETECT (0x2f) + +#define TCPC_V10_REG_RX_BYTE_CNT (0x30) +#define TCPC_V10_REG_RX_BUF_FRAME_TYPE (0x31) +#define TCPC_V10_REG_RX_HDR (0x32) +#define TCPC_V10_REG_RX_DATA (0x34) + +#define TCPC_V10_REG_TRANSMIT (0x50) +#define TCPC_V10_REG_TX_BYTE_CNT (0x51) +#define TCPC_V10_REG_TX_HDR (0x52) +#define TCPC_V10_REG_TX_DATA (0x54)/* through 0x6f */ + +#define TCPC_V10_REG_VBUS_SINK_DISCONNECT_THD (0x72) +#define TCPC_V10_REG_VBUS_STOP_DISCHARGE_THD (0x74) +#define TCPC_V10_REG_VBUS_VOLTAGE_ALARM_HI (0x76) +#define TCPC_V10_REG_VBUS_VOLTAGE_ALARM_LO (0x78) + +/* + * TCPC_V10_REG_ALERT (0x10) + * TCPC_V10_REG_ALERT_MASK (0x12) + */ +#define TCPC_V10_REG_ALERT_VENDOR_DEFINED (1<<15) +#define TCPC_V10_REG_VBUS_SINK_DISCONNECT (1<<11) +#define TCPC_V10_REG_RX_OVERFLOW (1<<10) +#define TCPC_V10_REG_ALERT_FAULT (1<<9) +#define TCPC_V10_REG_ALERT_LO_VOLT (1<<8) +#define TCPC_V10_REG_ALERT_HI_VOLT (1<<7) +#define TCPC_V10_REG_ALERT_TX_SUCCESS (1<<6) +#define TCPC_V10_REG_ALERT_TX_DISCARDED (1<<5) +#define TCPC_V10_REG_ALERT_TX_FAILED (1<<4) +#define TCPC_V10_REG_ALERT_RX_HARD_RST (1<<3) +#define TCPC_V10_REG_ALERT_RX_STATUS (1<<2) +#define TCPC_V10_REG_ALERT_POWER_STATUS (1<<1) +#define TCPC_V10_REG_ALERT_CC_STATUS (1<<0) + +/* + * TCPC_V10_REG_POWER_STATUS_MASK (0x14) + * TCPC_V10_REG_POWER_STATUS (0x1E) + */ + +#define TCPC_V10_REG_POWER_STATUS_TCPC_INITIAL (1<<6) +#define TCPC_V10_REG_POWER_STATUS_SRC_HV (1<<5) +#define TCPC_V10_REG_POWER_STATUS_SRC_VBUS (1<<4) +#define TCPC_V10_REG_POWER_STATUS_VBUS_PRES_DET (1<<3) +#define TCPC_V10_REG_POWER_STATUS_VBUS_PRES (1<<2) +#define TCPC_V10_REG_POWER_STATUS_VCONN_PRES (1<<1) +#define TCPC_V10_REG_POWER_STATUS_SINK_VBUS (1<<0) + +/* + * TCPC_V10_REG_FAULT_STATUS_MASK (0x15) + * TCPC_V10_REG_FAULT_STATUS (0x1F) + */ + +/* VCONN_OV should not be here */ +#define TCPC_V10_REG_FAULT_STATUS_VCONN_OV (1<<7) +#define TCPC_V10_REG_FAULT_STATUS_FORCE_OFF_VBUS (1<<6) +#define TCPC_V10_REG_FAULT_STATUS_AUTO_DISC_FAIL (1<<5) +#define TCPC_V10_REG_FAULT_STATUS_FORCE_DISC_FAIL (1<<4) +#define TCPC_V10_REG_FAULT_STATUS_VBUS_OC (1<<3) +#define TCPC_V10_REG_FAULT_STATUS_VBUS_OV (1<<2) +#define TCPC_V10_REG_FAULT_STATUS_VCONN_OC (1<<1) +#define TCPC_V10_REG_FAULT_STATUS_I2C_ERROR (1<<0) + +/* + * TCPC_V10_REG_ROLE_CTRL (0x1A) + */ + +#define TCPC_V10_REG_ROLE_CTRL_DRP (1<<6) + +#define TCPC_V10_REG_ROLE_CTRL_RES_SET(drp, rp, cc1, cc2) \ + ((drp) << 6 | (rp) << 4 | (cc2) << 2 | (cc1)) + +#define CC_RD 0x02 +#define CC_RP 0x01 +#define CC_OPEN 0x03 +#define CC_RA 0x00 + +/* + * TCPC_V10_REG_TCPC_CTRL (0x19) + */ + +#define TCPC_V10_REG_TCPC_CTRL_EN_LOOK4CONNECTION_ALERT (1<<6) +#define TCPC_V10_REG_TCPC_CTRL_EN_WDT (1<<5) +#define TCPC_V10_REG_TCPC_CTRL_BIST_TEST_MODE (1<<1) +#define TCPC_V10_REG_TCPC_CTRL_PLUG_ORIENT (1<<0) + +/* + * TCPC_V10_REG_FAULT_CTRL (0x1B) + */ + +#define TCPC_V10_REG_FAULT_CTRL_DIS_VCONN_OV (1<<7) +#define TCPC_V10_REG_FAULT_CTRL_DIS_SNK_VBUS_OC (1<<2) +#define TCPC_V10_REG_FAULT_CTRL_DIS_VCONN_OC (1<<0) + +/* + * TCPC_V10_REG_POWER_CTRL (0x1C) + */ + +#define TCPC_V10_REG_VBUS_MONITOR (1<<6) +#define TCPC_V10_REG_BLEED_DISC_EN (1<<3) +#define TCPC_V10_REG_FORCE_DISC_EN (1<<2) +#define TCPC_V10_REG_POWER_CTRL_VCONN (1<<0) + +/* + * TCPC_V10_REG_CC_STATUS (0x1D) + */ + +#define TCPC_V10_REG_CC_STATUS_DRP_TOGGLING (1<<5) +#define TCPC_V10_REG_CC_STATUS_DRP_RESULT(reg) (((reg) & 0x10) >> 4) +#define TCPC_V10_REG_CC_STATUS_CC2(reg) (((reg) & 0xc) >> 2) +#define TCPC_V10_REG_CC_STATUS_CC1(reg) ((reg) & 0x3) + +/* + * TCPC_V10_REG_COMMAND (0x23) + */ + +enum tcpm_v10_command { + TCPM_CMD_WAKE_I2C = 0x11, + TCPM_CMD_DISABLE_VBUS_DETECT = 0x22, + TCPM_CMD_ENABLE_VBUS_DETECT = 0x33, + TCPM_CMD_DISABLE_SINK_VBUS = 0x44, + TCPM_CMD_ENABLE_SINK_VBUS = 0x55, + TCPM_CMD_DISABLE_SOURCE_VBUS = 0x66, + TCPM_CMD_ENABLE_SOURCE_VBUS = 0x77, + TCPM_CMD_SOURCE_VBUS_HV = 0x88, + TCPM_CMD_LOOK_CONNECTION = 0x99, + TCPM_CMD_RX_ONE_MODE = 0xAA, + TCPM_CMD_I2C_IDLE = 0xFF, +}; + +/* + * TCPC_V10_REG_MSG_HDR_INFO (0x2e) + * According to PD30_Rev11 ECR, + * The sender of a GoodCRC Message should set : + * the Specification Revsiion field to 10b. + */ + +#define TCPC_V10_REG_MSG_HDR_INFO_SET(drole, prole) \ + ((drole) << 3 | ((PD_REV20) << 1) | (prole)) +#define TCPC_V10_REG_MSG_HDR_INFO_DROLE(reg) (((reg) & 0x8) >> 3) +#define TCPC_V10_REG_MSG_HDR_INFO_PROLE(reg) ((reg) & 0x1) + + +/* + * TCPC_V10_REG_TRANSMIT (0x50) + */ + +#ifdef CONFIG_USB_PD_REV30 +#define TCPC_V10_REG_TRANSMIT_SET(retry, type) \ + ((retry) << 4 | (type)) +#else +#define TCPC_V10_REG_TRANSMIT_SET(retry, type) \ + (PD_RETRY_COUNT << 4 | (type)) +#endif /* CONFIG_USB_PD_REV30 */ + +#endif /* STD_TCPCI_V10_H_ */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/tcpci.h b/drivers/usb/typec/mmi_tcpc/inc/tcpci.h new file mode 100644 index 000000000000..26e07aeccd12 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/tcpci.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_RT_TCPC_H +#define __LINUX_RT_TCPC_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_DUAL_ROLE_USB_INTF +#include +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + +#include "tcpci_core.h" + +#ifdef CONFIG_PD_DBG_INFO +#include "pd_dbg_info.h" +#endif /* CONFIG_PD_DBG_INFO */ + +#ifdef CONFIG_USB_POWER_DELIVERY +#include "pd_core.h" +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#define PE_STATE_FULL_NAME 0 + +#define TCPC_LOW_RP_DUTY (100) /* 10 % */ +#define TCPC_NORMAL_RP_DUTY (330) /* 33 % */ + +/* provide to TCPC interface */ +extern int tcpci_report_usb_port_changed(struct tcpc_device *tcpc); +extern int tcpci_set_wake_lock( + struct tcpc_device *tcpc, bool pd_lock, bool user_lock); +extern int tcpci_report_power_control(struct tcpc_device *tcpc, bool en); +extern int tcpc_typec_init(struct tcpc_device *tcpc, uint8_t typec_role); +extern void tcpc_typec_deinit(struct tcpc_device *tcpc); +extern int tcpc_dual_role_phy_init(struct tcpc_device *tcpc); + +extern struct tcpc_device *tcpc_device_register( + struct device *parent, struct tcpc_desc *tcpc_desc, + struct tcpc_ops *ops, void *drv_data); +extern void tcpc_device_unregister( + struct device *dev, struct tcpc_device *tcpc); + +extern int tcpc_schedule_init_work(struct tcpc_device *tcpc); + +extern void *tcpc_get_dev_data(struct tcpc_device *tcpc); +extern void tcpci_lock_typec(struct tcpc_device *tcpc); +extern void tcpci_unlock_typec(struct tcpc_device *tcpc); +extern int tcpci_alert(struct tcpc_device *tcpc); + +extern void tcpci_vbus_level_init( + struct tcpc_device *tcpc, uint16_t power_status); + +static inline int tcpci_check_vbus_valid(struct tcpc_device *tcpc) +{ + return tcpc->vbus_level >= TCPC_VBUS_VALID; +} + +int tcpci_check_vbus_valid_from_ic(struct tcpc_device *tcpc); +int tcpci_check_vsafe0v(struct tcpc_device *tcpc, bool detect_en); +int tcpci_alert_status_clear(struct tcpc_device *tcpc, uint32_t mask); +int tcpci_fault_status_clear(struct tcpc_device *tcpc, uint8_t status); +int tcpci_set_alert_mask(struct tcpc_device *tcpc, uint32_t mask); +int tcpci_get_alert_mask(struct tcpc_device *tcpc, uint32_t *mask); +int tcpci_get_alert_status(struct tcpc_device *tcpc, uint32_t *alert); +int tcpci_get_fault_status(struct tcpc_device *tcpc, uint8_t *fault); +int tcpci_get_power_status(struct tcpc_device *tcpc, uint16_t *pw_status); +int tcpci_init(struct tcpc_device *tcpc, bool sw_reset); +int tcpci_init_alert_mask(struct tcpc_device *tcpc); + +int tcpci_get_cc(struct tcpc_device *tcpc); +int tcpci_set_cc(struct tcpc_device *tcpc, int pull); +int tcpci_set_polarity(struct tcpc_device *tcpc, int polarity); +int tcpci_set_low_rp_duty(struct tcpc_device *tcpc, bool low_rp); +int tcpci_set_vconn(struct tcpc_device *tcpc, int enable); + +int tcpci_is_low_power_mode(struct tcpc_device *tcpc); +int tcpci_set_low_power_mode(struct tcpc_device *tcpc, bool en, int pull); +int tcpci_idle_poll_ctrl(struct tcpc_device *tcpc, bool en, bool lock); +int tcpci_set_watchdog(struct tcpc_device *tcpc, bool en); +int tcpci_alert_vendor_defined_handler(struct tcpc_device *tcpc); +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC +int tcpci_is_vsafe0v(struct tcpc_device *tcpc); +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + +#ifdef CONFIG_WATER_DETECTION +int tcpci_is_water_detected(struct tcpc_device *tcpc); +int tcpci_set_water_protection(struct tcpc_device *tcpc, bool en); +int tcpci_set_usbid_polling(struct tcpc_device *tcpc, bool en); +int tcpci_notify_wd_status(struct tcpc_device *tcpc, bool water_detected); +#endif /* CONFIG_WATER_DETECTION */ + +#ifdef CONFIG_CABLE_TYPE_DETECTION +int tcpci_notify_cable_type(struct tcpc_device *tcpc); +#endif /* CONFIG_CABLE_TYPE_DETECTION */ + +#ifdef CONFIG_USB_POWER_DELIVERY + +int tcpci_set_msg_header(struct tcpc_device *tcpc, + uint8_t power_role, uint8_t data_role); + +int tcpci_set_rx_enable(struct tcpc_device *tcpc, uint8_t enable); + +int tcpci_protocol_reset(struct tcpc_device *tcpc); + +int tcpci_get_message(struct tcpc_device *tcpc, + uint32_t *payload, uint16_t *head, enum tcpm_transmit_type *type); + +int tcpci_transmit(struct tcpc_device *tcpc, + enum tcpm_transmit_type type, uint16_t header, const uint32_t *data); + +int tcpci_set_bist_test_mode(struct tcpc_device *tcpc, bool en); + +int tcpci_set_bist_carrier_mode(struct tcpc_device *tcpc, uint8_t pattern); + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD +int tcpci_retransmit(struct tcpc_device *tcpc); +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + +int tcpci_notify_typec_state(struct tcpc_device *tcpc); + +int tcpci_notify_role_swap( + struct tcpc_device *tcpc, uint8_t event, uint8_t role); +int tcpci_notify_pd_state(struct tcpc_device *tcpc, uint8_t connect); + +int tcpci_set_intrst(struct tcpc_device *tcpc, bool en); +int tcpci_enable_watchdog(struct tcpc_device *tcpc, bool en); + +int tcpci_source_vbus(struct tcpc_device *tcpc, uint8_t type, int mv, int ma); +int tcpci_sink_vbus(struct tcpc_device *tcpc, uint8_t type, int mv, int ma); +int tcpci_disable_vbus_control(struct tcpc_device *tcpc); +int tcpci_notify_attachwait_state(struct tcpc_device *tcpc, bool as_sink); +int tcpci_enable_auto_discharge(struct tcpc_device *tcpc, bool en); +int tcpci_enable_force_discharge(struct tcpc_device *tcpc, bool en, int mv); + +#ifdef CONFIG_USB_POWER_DELIVERY + +int tcpci_notify_hard_reset_state(struct tcpc_device *tcpc, uint8_t state); + +int tcpci_enter_mode(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, uint32_t mode); +int tcpci_exit_mode(struct tcpc_device *tcpc, uint16_t svid); + +#ifdef CONFIG_USB_PD_ALT_MODE +int tcpci_report_hpd_state(struct tcpc_device *tcpc, uint32_t dp_status); +int tcpci_dp_status_update(struct tcpc_device *tcpc, uint32_t dp_status); +int tcpci_dp_configure(struct tcpc_device *tcpc, uint32_t dp_config); +int tcpci_dp_attention(struct tcpc_device *tcpc, uint32_t dp_status); + +int tcpci_dp_notify_status_update_done( + struct tcpc_device *tcpc, uint32_t dp_status, bool ack); + +int tcpci_dp_notify_config_start(struct tcpc_device *tcpc); +int tcpci_dp_notify_config_done(struct tcpc_device *tcpc, + uint32_t local_cfg, uint32_t remote_cfg, bool ack); +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM +int tcpci_notify_uvdm(struct tcpc_device *tcpc, bool ack); +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC +int tcpci_dc_notify_en_unlock(struct tcpc_device *tcpc); +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +#ifdef CONFIG_USB_PD_REV30 + +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +int tcpci_notify_alert(struct tcpc_device *tcpc, uint32_t ado); +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE +int tcpci_notify_status(struct tcpc_device *tcpc, struct pd_status *sdb); +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO +int tcpci_notify_request_bat_info( + struct tcpc_device *tcpc, enum pd_battery_reference ref); +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ +#endif /* CONFIG_USB_PD_REV30 */ + +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#endif /* #ifndef __LINUX_RT_TCPC_H */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/tcpci_config.h b/drivers/usb/typec/mmi_tcpc/inc/tcpci_config.h new file mode 100644 index 000000000000..18a9a76ea494 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/tcpci_config.h @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_TCPC_CONFIG_H +#define __LINUX_TCPC_CONFIG_H + +#if IS_ENABLED(CONFIG_TCPC_CLASS) +/* default config */ + +/* #define CONFIG_TYPEC_USE_DIS_VBUS_CTRL */ +#define CONFIG_TYPEC_POWER_CTRL_INIT + +#define CONFIG_TYPEC_CAP_TRY_SOURCE +#define CONFIG_TYPEC_CAP_TRY_SINK + +#define CONFIG_TYPEC_CAP_DBGACC +#define CONFIG_TYPEC_CAP_DBGACC_SNK +#define CONFIG_TYPEC_CAP_CUSTOM_SRC +#define CONFIG_TYPEC_CAP_NORP_SRC +#define CONFIG_COMPATIBLE_APPLE_TA + +#if 0 /* CONFIG_MTK_GAUGE_VERSION == 30 */ +#define CONFIG_RECV_BAT_ABSENT_NOTIFY +#endif /* CONFIG_MTK_GAUGE_VERSION */ + +/* #define CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_DELAY */ +#define CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_TIMEOUT + +#define CONFIG_TYPEC_CHECK_LEGACY_CABLE +#define CONFIG_TYPEC_CHECK_LEGACY_CABLE2 +#define CONFIG_TYPEC_LEGACY2_AUTO_RECYCLE +/* #define CONFIG_TYPEC_CHECK_SRC_UNATTACH_OPEN */ + +#define CONFIG_TYPEC_CAP_RA_DETACH +#define CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG + +#define CONFIG_TYPEC_CAP_POWER_OFF_CHARGE + +#define CONFIG_TYPEC_CAP_ROLE_SWAP +#define CONFIG_TYPEC_CAP_ROLE_SWAP_TOUT 1200 + +#define CONFIG_TYPEC_CAP_DISCHARGE_TOUT 50 + +#define CONFIG_TYPEC_CAP_AUTO_DISCHARGE +/* #define CONFIG_TCPC_AUTO_DISCHARGE_IC */ + +#define CONFIG_TYPEC_CAP_FORCE_DISCHARGE +/* #define CONFIG_TCPC_FORCE_DISCHARGE_IC */ +#define CONFIG_TCPC_FORCE_DISCHARGE_EXT + +#define CONFIG_TYPEC_CAP_AUDIO_ACC_SINK_VBUS + +#define CONFIG_TYPEC_CAP_LOW_RP_DUTY +#define CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY + +/* #define CONFIG_TYPEC_CAP_CUSTOM_HV */ + +#define CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SNK +/* #define CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SRC */ + +#define CONFIG_TCPC_ATTACH_WAKE_LOCK_TOUT 5000 + +/* #define CONFIG_TCPC_LOG_WITH_PORT_NAME */ + +#ifdef CONFIG_TCPC_LOG_WITH_PORT_NAME +#define CONFIG_TCPC_DBG_PRESTR "{%s}TCPC-" +#else +#define CONFIG_TCPC_DBG_PRESTR "TCPC-" +#endif /* CONFIG_TCPC_LOG_WITH_PORT_NAME */ + +/* + * USB 2.0 & 3.0 current + * Unconfigured : 100 / 150 mA + * Configured : 500 / 900 mA + * http://www.testusb.com/power_issue.htm + */ + +#define CONFIG_TYPEC_SNK_CURR_DFT 100 +#define CONFIG_TYPEC_SRC_CURR_DFT 500 +#define CONFIG_TYPEC_SNK_CURR_LIMIT 0 + +#define CONFIG_TCPC_SOURCE_VCONN +#define CONFIG_TCPC_VCONN_SUPPLY_MODE + +#define CONFIG_TCPC_VSAFE0V_DETECT +/* #define CONFIG_TCPC_VSAFE0V_DETECT_EXT */ +#define CONFIG_TCPC_VSAFE0V_DETECT_IC + +#define CONFIG_TCPC_LPM_CONFIRM +#define CONFIG_TCPC_LPM_POSTPONE + +#define CONFIG_TCPC_LOW_POWER_MODE +#define CONFIG_TCPC_CLOCK_GATING + +/* #define CONFIG_TCPC_WATCHDOG_EN */ +/* #define CONFIG_TCPC_INTRST_EN */ +#define CONFIG_TCPC_I2CRST_EN + +#define CONFIG_TCPC_SHUTDOWN_CC_DETACH +#define CONFIG_TCPC_SHUTDOWN_VBUS_DISABLE + +#define CONFIG_TCPC_NOTIFIER_LATE_SYNC + +#define CONFIG_TCPC_NOTIFICATION_NON_BLOCKING + +#ifdef CONFIG_USB_POWER_DELIVERY + +/* Experimental for CodeSize */ +#define CONFIG_USB_PD_DR_SWAP +#define CONFIG_USB_PD_PR_SWAP +#define CONFIG_USB_PD_VCONN_SWAP +#define CONFIG_USB_PD_PE_SINK +#define CONFIG_USB_PD_PE_SOURCE +/* #define CONFIG_USB_PD_DISABLE_PE */ + +#define CONFIG_USB_PD_TCPM_CB_RETRY 3 +#define CONFIG_USB_PD_TCPM_CB_2ND +#define CONFIG_USB_PD_BLOCK_TCPM + +/* #define CONFIG_USB_PD_RICHTEK_UVDM */ + +/********************************************************** + * Mode Operation + **********************************************************/ + +#define CONFIG_USB_PD_MODE_OPERATION + +#ifdef CONFIG_USB_PD_MODE_OPERATION + +#define CONFIG_USB_PD_ATTEMP_ENTER_MODE + +#define CONFIG_USB_PD_ALT_MODE +#ifdef CONFIG_USB_PD_ALT_MODE +#define CONFIG_USB_PD_ALT_MODE_DFP +#define CONFIG_USB_PD_ALT_MODE_RTDC +#endif /* CONFIG_USB_PD_ALT_MODE */ + +/* #define CONFIG_USB_PD_DP_CHECK_CABLE */ +/* #define CONFIG_USB_PD_RTDC_CHECK_CABLE */ + +#endif /* CONFIG_USB_PD_MODE_OPERATION */ + +/********************************************************** + * PD revision 3.0 feature + **********************************************************/ + +#define CONFIG_USB_PD_REV30 + +#ifdef CONFIG_USB_PD_REV30 + +#define CONFIG_USB_PD_REV30_SYNC_SPEC_REV +#define CONFIG_USB_PD_REV30_COLLISION_AVOID + +/* + * If DUT send a PD command immediately after Policy Engine is ready, + * it may interrupt the compliance test process and getting a failed result. + * even you make sure the CC state is SinkTxNG or SinkTxOK. + * + * SRC_FLOW_DELAY_STARTUP: For Ellisys + * SNK_FLOW_DELAY_STARTUP: For MQP + */ + +#define CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP +#define CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP + +#define CONFIG_USB_PD_REV30_DISCOVER_CABLE_WITH_VCONN + +/* PD30 Common Feature */ + +#define CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL +#define CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + +#define CONFIG_USB_PD_REV30_BAT_CAP_LOCAL +#define CONFIG_USB_PD_REV30_BAT_CAP_REMOTE + +#define CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL +#define CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE + +#define CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL +#define CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + +#define CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL +#define CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE + +#define CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL +#define CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE + +#define CONFIG_USB_PD_REV30_ALERT_LOCAL +#define CONFIG_USB_PD_REV30_ALERT_REMOTE + +#define CONFIG_USB_PD_REV30_STATUS_LOCAL +#define CONFIG_USB_PD_REV30_STATUS_REMOTE + +/* #define CONFIG_USB_PD_REV30_CHUNKING_BY_PE */ + +#define CONFIG_USB_PD_REV30_PPS_SINK +/* #define CONFIG_USB_PD_REV30_PPS_SOURCE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL +#define CONFIG_USB_PD_REV30_STATUS_LOCAL_TEMP +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL +#define CONFIG_USB_PD_REV30_BAT_INFO +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL +#undef CONFIG_USB_PD_REV30_BAT_INFO +#define CONFIG_USB_PD_REV30_BAT_INFO +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL +#define CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL +#undef CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY +#define CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL +#define CONFIG_USB_PD_DPM_AUTO_SEND_ALERT +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +#define CONFIG_USB_PD_DPM_AUTO_GET_STATUS +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +#define CONFIG_MTK_HANDLE_PPS_TIMEOUT + +#endif /* CONFIG_USB_PD_REV30 */ + +/********************************************************** + * PD direct charge support + **********************************************************/ + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC +#define CONFIG_USB_PD_DIRECT_CHARGE +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +#undef CONFIG_USB_PD_DIRECT_CHARGE +#define CONFIG_USB_PD_DIRECT_CHARGE +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +#define CONFIG_USB_PD_KEEP_PARTNER_ID +#define CONFIG_USB_PD_KEEP_SVIDS +#define CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#define CONFIG_USB_PD_DFP_READY_DISCOVER_ID +#define CONFIG_USB_PD_RESET_CABLE + +#define CONFIG_USB_PD_RANDOM_FLOW_DELAY + +#define CONFIG_USB_PD_DFP_FLOW_DELAY +#define CONFIG_USB_PD_DFP_FLOW_DELAY_STARTUP +#define CONFIG_USB_PD_DFP_FLOW_DELAY_DRSWAP +#define CONFIG_USB_PD_DFP_FLOW_DELAY_RESET + +/* Only in startup */ +#define CONFIG_USB_PD_UFP_FLOW_DELAY +#define CONFIG_USB_PD_VCONN_STABLE_DELAY +#define CONFIG_USB_PD_VCONN_SAFE5V_ONLY + +#define CONFIG_USB_PD_ATTEMP_DISCOVER_ID +#define CONFIG_USB_PD_ATTEMP_DISCOVER_SVID + +#define CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN +#define CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN + +#define CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY + +#define CONFIG_USB_PD_CUSTOM_VDM + +#ifdef CONFIG_USB_PD_CUSTOM_VDM +#define CONFIG_USB_PD_SVDM +#define CONFIG_USB_PD_UVDM +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK +/* #define CONFIG_USB_PD_CUSTOM_DBGACC */ +#endif /* CONFIG_TYPEC_CAP_DBGACC_SNK */ + +/* S/W patch for ESD issue: repeat HReset Alert */ +/* #define CONFIG_USB_PD_RECV_HRESET_COUNTER */ + +/* S/W patch for NoGoodCRC if SNK_DFT */ +#define CONFIG_USB_PD_SNK_DFT_NO_GOOD_CRC + +/* S/W patch for NoGoodCRC after PR_SWAP (repeat PS_RDY) */ +#define CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP + +/* S/W patch for delayed ps_change related to PS_RDY during PR_SWAP */ +#define CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP + +/* + * S/W patch for INT handler was stuck by other task (system busy) + * + * If the communication fails due to a timeout, + * check the rx buffer is empty or not. + * + * If the rx buffer isn't empty, postpone timer. + */ + +#define CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT +#define CONFIG_USB_PD_ONLY_PRINT_SYSTEM_BUSY + +/* + * S/W patch for + * If the CC pin is shorted to ground and our receive ability is better, + * then we may receive message however always send failed. + * + * For the source role, it becomes an infinite hard reset loop. + * For the sink role, it becomes an infinite error recovery loop. + * + * RENEGOTIATION_COUNTER: + * Auto error recovery + * if retried hard reset many times still negotiation failed. + * + * ERROR_RECOVERY_ONCE: + * If we have tried an error recovery, + * using pure typec mode during next time we reconnect. + * + */ + +#define CONFIG_USB_PD_RENEGOTIATION_COUNTER +#define CONFIG_USB_PD_ERROR_RECOVERY_ONCE + +#define CONFIG_USB_PD_IGNORE_HRESET_COMPLETE_TIMER +#define CONFIG_USB_PD_DROP_REPEAT_PING +#define CONFIG_USB_PD_CHECK_DATA_ROLE +#define CONFIG_USB_PD_RETRY_CRC_DISCARD +#define CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST + +#define CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW +/* #define CONFIG_USB_PD_SNK_IGNORE_HRESET_IF_TYPEC_ONLY */ +#define CONFIG_USB_PD_SNK_STANDBY_POWER +#define CONFIG_USB_PD_SNK_GOTOMIN + +/* #define CONFIG_USB_PD_SRC_HIGHCAP_POWER */ +#define CONFIG_USB_PD_SRC_TRY_PR_SWAP_IF_BAD_PW + +#define CONFIG_USB_PD_TRANSMIT_BIST2 +#define CONFIG_USB_PD_POSTPONE_VDM +#define CONFIG_USB_PD_POSTPONE_RETRY_VDM +#define CONFIG_USB_PD_POSTPONE_FIRST_VDM +#define CONFIG_USB_PD_POSTPONE_OTHER_VDM +/* #define CONFIG_USB_PD_STOP_SEND_VDM_IF_RX_BUSY */ +#define CONFIG_USB_PD_STOP_REPLY_VDM_IF_RX_BUSY + +/* #define CONFIG_USB_PD_SAFE0V_DELAY */ +/* #define CONFIG_USB_PD_SAFE0V_TIMEOUT */ + +#ifndef CONFIG_USB_PD_DPM_SVDM_RETRY +#define CONFIG_USB_PD_DPM_SVDM_RETRY 2 +#endif /* CONFIG_USB_PD_DPM_SVDM_RETRY */ + +#ifndef CONFIG_USB_PD_DFP_FLOW_RETRY_MAX +#define CONFIG_USB_PD_DFP_FLOW_RETRY_MAX 2 +#endif /* CONFIG_USB_PD_DFP_FLOW_RETRY_MAX */ + +#ifndef CONFIG_USB_PD_VBUS_STABLE_TOUT +#define CONFIG_USB_PD_VBUS_STABLE_TOUT 125 +#endif /* CONFIG_USB_PD_VBUS_STABLE_TOUT */ + +#ifndef CONFIG_USB_PD_VBUS_PRESENT_TOUT +#define CONFIG_USB_PD_VBUS_PRESENT_TOUT 20 +#endif /* CONFIG_USB_PD_VBUS_PRESENT_TOUT */ + +#ifndef CONFIG_USB_PD_CUSTOM_VDM_TOUT +#define CONFIG_USB_PD_CUSTOM_VDM_TOUT 500 +#endif /* CONFIG_USB_PD_CUSTOM_VDM_TOUT */ + +#ifndef CONFIG_USB_PD_VCONN_READY_TOUT +#define CONFIG_USB_PD_VCONN_READY_TOUT 5 +#endif /* CONFIG_USB_PD_VCONN_READY_TOUT */ + +#ifndef CONFIG_USB_PD_DFP_FLOW_DLY +#define CONFIG_USB_PD_DFP_FLOW_DLY 30 +#endif /* CONFIG_USB_PD_DFP_FLOW_DLY */ + +#ifndef CONFIG_USB_PD_UFP_FLOW_DLY +#define CONFIG_USB_PD_UFP_FLOW_DLY 300 +#endif /* CONFIG_USB_PD_UFP_FLOW_DLY */ + +#ifndef CONFIG_USB_PD_PPS_REQUEST_INTERVAL +#define CONFIG_USB_PD_PPS_REQUEST_INTERVAL 7000 +#endif /* CONFIG_USB_PD_PPS_REQUEST_INTERVAL */ + +#undef CONFIG_USB_PD_WAIT_BC12 +#endif /* CONFIG_USB_POWER_DELIVERY */ + +/* debug config */ +#define CONFIG_USB_PD_DBG_ALERT_STATUS +/* #define CONFIG_USB_PD_DBG_SKIP_ALERT_HANDLER */ +#define CONFIG_USB_PD_DBG_DP_DFP_D_AUTO_UPDATE + +/********************************************************** + * TypeC Shield Protection + **********************************************************/ + +#ifdef CONFIG_MTK_TYPEC_WATER_DETECT +#define CONFIG_WATER_DETECTION +#endif /* CONFIG_MTK_TYPEC_WATER_DETECT */ + +#ifdef CONFIG_WATER_DETECTION +#define CONFIG_WD_SBU_POLLING +/* #define CONFIG_WATER_CALIBRATION */ +#define CONFIG_WD_SBU_CALIB_INIT 1800 /* mV */ +#define CONFIG_WD_SBU_PL_BOUND 200 /* mV */ +#define CONFIG_WD_SBU_PH_AUDDEV 200 /* mV */ +#define CONFIG_WD_SBU_PH_LBOUND 1180 /* mV */ +#define CONFIG_WD_SBU_PH_LBOUND1_C2C 2850 /* mV */ +#define CONFIG_WD_SBU_PH_UBOUND1_C2C 3150 /* mV */ +#define CONFIG_WD_SBU_PH_UBOUND2_C2C 3800 /* mV */ +#define CONFIG_WD_SBU_AUD_UBOUND 1600 /* mV */ +#define CONFIG_WD_PROTECT_RETRY_COUNT 3 +#endif /* CONFIG_WATER_DETECTION */ + +#ifdef CONFIG_WD_SBU_POLLING +#define CONFIG_WD_POLLING_ONLY +#endif /* CONFIG_WD_SBU_POLLING */ + +#define CONFIG_CABLE_TYPE_DETECTION + +#endif /* CONFIG_TCPC_CLASS */ +#endif /* __LINUX_TCPC_CONFIG_H */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/tcpci_core.h b/drivers/usb/typec/mmi_tcpc/inc/tcpci_core.h new file mode 100644 index 000000000000..e7c0f2d93097 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/tcpci_core.h @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_RT_TCPCI_CORE_H +#define __LINUX_RT_TCPCI_CORE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tcpm.h" +#include "tcpci_timer.h" +#include "tcpci_config.h" + +#ifdef CONFIG_USB_POWER_DELIVERY +#include "pd_core.h" +#ifdef CONFIG_USB_PD_WAIT_BC12 +#include +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif + +/* The switch of log message */ +#define TYPEC_INFO_ENABLE 1 +#define TYPEC_INFO2_ENABLE 1 +#define PE_EVENT_DBG_ENABLE 1 +#define PE_STATE_INFO_ENABLE 1 +#define TCPC_INFO_ENABLE 1 +#define TCPC_TIMER_DBG_EN 0 +#define TCPC_TIMER_INFO_EN 0 +#define PE_INFO_ENABLE 1 +#define TCPC_DBG_ENABLE 0 +#define TCPC_DBG2_ENABLE 0 +#define DPM_INFO_ENABLE 1 +#define DPM_INFO2_ENABLE 1 +#define DPM_DBG_ENABLE 0 +#define PD_ERR_ENABLE 1 +#define PE_DBG_ENABLE 0 +#define TYPEC_DBG_ENABLE 0 + + +#define DP_INFO_ENABLE 1 +#define DP_DBG_ENABLE 1 + +#define UVDM_INFO_ENABLE 1 +#define TCPM_DBG_ENABLE 1 + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC +#define DC_INFO_ENABLE 1 +#define DC_DBG_ENABLE 1 +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +#define TCPC_ENABLE_ANYMSG \ + ((TCPC_DBG_ENABLE)|(TCPC_DBG2_ENABLE)|\ + (DPM_DBG_ENABLE)|\ + (PD_ERR_ENABLE)|(PE_INFO_ENABLE)|(TCPC_TIMER_INFO_EN)|\ + (PE_DBG_ENABLE)|(PE_EVENT_DBG_ENABLE)|\ + (PE_STATE_INFO_ENABLE)|(TCPC_INFO_ENABLE)|\ + (TCPC_TIMER_DBG_EN)|(TYPEC_DBG_ENABLE)|\ + (TYPEC_INFO_ENABLE)|\ + (DP_INFO_ENABLE)|(DP_DBG_ENABLE)|\ + (UVDM_INFO_ENABLE)|(TCPM_DBG_ENABLE)) + +/* Disable VDM DBG Msg */ +#define PE_STATE_INFO_VDM_DIS 0 +#define PE_EVT_INFO_VDM_DIS 0 +#define PE_DBG_RESET_VDM_DIS 1 + +#define PD_BUG_ON(x) WARN_ON(x) + +extern struct class *tcpc_class; +struct tcpc_device; + +struct tcpc_desc { + uint8_t role_def; + uint8_t rp_lvl; + uint8_t vconn_supply; + int notifier_supply_num; + char *name; +}; + +int tcpc_device_irq_enable(struct tcpc_device *tcpc); + +/*---------------------------------------------------------------------------*/ + +#ifdef CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SNK +#define CONFIG_TYPEC_NOTIFY_ATTACHWAIT +#endif /* CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SNK */ + +#ifdef CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SRC +#undef CONFIG_TYPEC_NOTIFY_ATTACHWAIT +#define CONFIG_TYPEC_NOTIFY_ATTACHWAIT +#endif /* CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SNK */ + + +#ifdef CONFIG_TCPC_FORCE_DISCHARGE_EXT +#define CONFIG_TCPC_EXT_DISCHARGE +#endif /* CONFIG_TCPC_FORCE_DISCHARGE_EXT */ + +/*---------------------------------------------------------------------------*/ + +/* TCPC Power Register Define */ +#define TCPC_REG_POWER_STATUS_EXT_VSAFE0V (1<<15) /* extend */ +#define TCPC_REG_POWER_STATUS_VBUS_PRES (1<<2) + +/* TCPC Alert Register Define */ +#define TCPC_REG_ALERT_EXT_RA_DETACH (1<<(16+5)) +#define TCPC_REG_ALERT_EXT_WATCHDOG (1<<(16+2)) +#define TCPC_REG_ALERT_EXT_VBUS_80 (1<<(16+1)) +#define TCPC_REG_ALERT_EXT_WAKEUP (1<<(16+0)) + +#define TCPC_REG_ALERT_VBUS_DISCNCT (1<<11) +#define TCPC_REG_ALERT_RX_BUF_OVF (1<<10) +#define TCPC_REG_ALERT_FAULT (1<<9) +#define TCPC_REG_ALERT_V_ALARM_LO (1<<8) +#define TCPC_REG_ALERT_V_ALARM_HI (1<<7) +#define TCPC_REG_ALERT_TX_SUCCESS (1<<6) +#define TCPC_REG_ALERT_TX_DISCARDED (1<<5) +#define TCPC_REG_ALERT_TX_FAILED (1<<4) +#define TCPC_REG_ALERT_RX_HARD_RST (1<<3) +#define TCPC_REG_ALERT_RX_STATUS (1<<2) +#define TCPC_REG_ALERT_POWER_STATUS (1<<1) +#define TCPC_REG_ALERT_CC_STATUS (1<<0) + +#define TCPC_REG_ALERT_RX_MASK \ + (TCPC_REG_ALERT_RX_STATUS | TCPC_REG_ALERT_RX_BUF_OVF) + +#define TCPC_REG_ALERT_RX_ALL_MASK \ + (TCPC_REG_ALERT_RX_MASK | TCPC_REG_ALERT_RX_HARD_RST) + +#define TCPC_REG_ALERT_HRESET_SUCCESS \ + (TCPC_REG_ALERT_TX_SUCCESS | TCPC_REG_ALERT_TX_FAILED) + +#define TCPC_REG_ALERT_TX_MASK (TCPC_REG_ALERT_TX_SUCCESS | \ + TCPC_REG_ALERT_TX_FAILED | TCPC_REG_ALERT_TX_DISCARDED) + +#define TCPC_REG_ALERT_TXRX_MASK \ + (TCPC_REG_ALERT_TX_MASK | TCPC_REG_ALERT_RX_MASK) + +/* TCPC Behavior Flags */ +#define TCPC_FLAGS_RETRY_CRC_DISCARD (1<<0) +#define TCPC_FLAGS_WAIT_HRESET_COMPLETE (1<<1) /* Always true */ +#define TCPC_FLAGS_CHECK_CC_STABLE (1<<2) +#define TCPC_FLAGS_LPM_WAKEUP_WATCHDOG (1<<3) +#define TCPC_FLAGS_CHECK_RA_DETACHE (1<<4) +#define TCPC_FLAGS_PREFER_LEGACY2 (1<<5) +#define TCPC_FLAGS_DISABLE_LEGACY (1<<6) + +#define TCPC_FLAGS_PD_REV30 (1<<7) + +#define TCPC_FLAGS_WATCHDOG_EN (1<<8) +#define TCPC_FLAGS_WATER_DETECTION (1<<9) +#define TCPC_FLAGS_CABLE_TYPE_DETECTION (1<<10) +#define TCPC_FLAGS_VCONN_SAFE5V_ONLY (1<<11) +#define TCPC_FLAGS_ALERT_V10 (1<<12) + +enum tcpc_cc_pull { + TYPEC_CC_RA = 0, + TYPEC_CC_RP = 1, + TYPEC_CC_RD = 2, + TYPEC_CC_OPEN = 3, + TYPEC_CC_DRP = 4, /* from Rd */ + + TYPEC_CC_RP_DFT = 1, /* 0x00 + 1 */ + TYPEC_CC_RP_1_5 = 9, /* 0x08 + 1*/ + TYPEC_CC_RP_3_0 = 17, /* 0x10 + 1 */ + + TYPEC_CC_DRP_DFT = 4, /* 0x00 + 4 */ + TYPEC_CC_DRP_1_5 = 12, /* 0x08 + 4 */ + TYPEC_CC_DRP_3_0 = 20, /* 0x10 + 4 */ +}; + +#define TYPEC_CC_PULL_GET_RES(pull) (pull & 0x07) +#define TYPEC_CC_PULL_GET_RP_LVL(pull) ((pull & 0x18) >> 3) + +enum tcpm_rx_cap_type { + TCPC_RX_CAP_SOP = 1 << 0, + TCPC_RX_CAP_SOP_PRIME = 1 << 1, + TCPC_RX_CAP_SOP_PRIME_PRIME = 1 << 2, + TCPC_RX_CAP_SOP_DEBUG_PRIME = 1 << 3, + TCPC_RX_CAP_SOP_DEBUG_PRIME_PRIME = 1 << 4, + TCPC_RX_CAP_HARD_RESET = 1 << 5, + TCPC_RX_CAP_CABLE_RESET = 1 << 6, +}; + +struct tcpc_ops { + int (*init)(struct tcpc_device *tcpc, bool sw_reset); + int (*init_alert_mask)(struct tcpc_device *tcpc); + int (*alert_status_clear)(struct tcpc_device *tcpc, uint32_t mask); + int (*fault_status_clear)(struct tcpc_device *tcpc, uint8_t status); + int (*set_alert_mask)(struct tcpc_device *tcpc, uint32_t mask); + int (*get_alert_mask)(struct tcpc_device *tcpc, uint32_t *mask); + int (*get_alert_status)(struct tcpc_device *tcpc, uint32_t *alert); + int (*get_power_status)(struct tcpc_device *tcpc, uint16_t *pwr_status); + int (*get_fault_status)(struct tcpc_device *tcpc, uint8_t *status); + int (*get_cc)(struct tcpc_device *tcpc, int *cc1, int *cc2); + int (*set_cc)(struct tcpc_device *tcpc, int pull); + int (*set_polarity)(struct tcpc_device *tcpc, int polarity); + int (*set_low_rp_duty)(struct tcpc_device *tcpc, bool low_rp); + int (*set_vconn)(struct tcpc_device *tcpc, int enable); + int (*deinit)(struct tcpc_device *tcpc); + int (*alert_vendor_defined_handler)(struct tcpc_device *tcpc); + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + int (*is_vsafe0v)(struct tcpc_device *tcpc); +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + +#ifdef CONFIG_WATER_DETECTION + int (*is_water_detected)(struct tcpc_device *tcpc); + int (*set_water_protection)(struct tcpc_device *tcpc, bool en); + int (*set_usbid_polling)(struct tcpc_device *tcpc, bool en); +#endif /* CONFIG_WATER_DETECTION */ + +#ifdef CONFIG_TCPC_LOW_POWER_MODE + int (*is_low_power_mode)(struct tcpc_device *tcpc); + int (*set_low_power_mode)(struct tcpc_device *tcpc, bool en, int pull); +#endif /* CONFIG_TCPC_LOW_POWER_MODE */ + + int (*set_watchdog)(struct tcpc_device *tcpc, bool en); + +#ifdef CONFIG_TCPC_INTRST_EN + int (*set_intrst)(struct tcpc_device *tcpc, bool en); +#endif /* CONFIG_TCPC_INTRST_EN */ + +#ifdef CONFIG_TYPEC_CAP_AUTO_DISCHARGE +#ifdef CONFIG_TCPC_AUTO_DISCHARGE_IC + int (*set_auto_discharge)(struct tcpc_device *tcpc, bool en); +#endif /* CONFIG_TCPC_AUTO_DISCHARGE_IC */ +#endif /* CONFIG_TYPEC_CAP_AUTO_DISCHARGE */ + +#ifdef CONFIG_USB_POWER_DELIVERY + int (*set_msg_header)(struct tcpc_device *tcpc, + uint8_t power_role, uint8_t data_role); + int (*set_rx_enable)(struct tcpc_device *tcpc, uint8_t enable); + int (*get_message)(struct tcpc_device *tcpc, uint32_t *payload, + uint16_t *head, enum tcpm_transmit_type *type); + int (*protocol_reset)(struct tcpc_device *tcpc); + int (*transmit)(struct tcpc_device *tcpc, + enum tcpm_transmit_type type, + uint16_t header, const uint32_t *data); + int (*set_bist_test_mode)(struct tcpc_device *tcpc, bool en); + int (*set_bist_carrier_mode)(struct tcpc_device *tcpc, uint8_t pattern); + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + int (*retransmit)(struct tcpc_device *tcpc); +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + +#ifdef CONFIG_TYPEC_CAP_FORCE_DISCHARGE +#ifdef CONFIG_TCPC_FORCE_DISCHARGE_IC + int (*set_force_discharge)(struct tcpc_device *tcpc, bool en, int mv); +#endif /* CONFIG_TCPC_FORCE_DISCHARGE_IC */ +#endif /* CONFIG_TYPEC_CAP_FORCE_DISCHARGE */ + +#endif /* CONFIG_USB_POWER_DELIVERY */ +}; + +#define TCPC_VBUS_SOURCE_0V (0) +#define TCPC_VBUS_SOURCE_5V (5000) + +#define TCPC_VBUS_SINK_0V (0) +#define TCPC_VBUS_SINK_5V (5000) + +#define TCPC_LOW_POWER_MODE_RETRY 5 + +/* + * Confirm DUT is connected to legacy cable or not + * after suupect_counter > this threshold (0 = always check) + */ + +#define TCPC_LEGACY_CABLE_SUSPECT_THD 1 + +/* + * Try another s/w workaround after retry_counter more than this value + * Try which soltuion first is determined by tcpc_flags + */ + +#define TCPC_LEGACY_CABLE_RETRY_SOLUTION 2 + +struct tcpc_managed_res; + +/* + * tcpc device + */ + +struct tcpc_device { + struct i2c_client *client; + struct tcpc_ops *ops; + void *drv_data; + struct tcpc_desc desc; + struct device dev; + bool wake_lock_user; + uint8_t wake_lock_pd; + struct wakeup_source *attach_wake_lock; + struct wakeup_source *detach_wake_lock; + + /* For tcpc timer & event */ + uint32_t timer_handle_index; + struct hrtimer tcpc_timer[PD_TIMER_NR]; + + struct alarm wake_up_timer; + struct delayed_work wake_up_work; + struct wakeup_source *wakeup_wake_lock; + + ktime_t last_expire[PD_TIMER_NR]; + struct mutex access_lock; + struct mutex typec_lock; + struct mutex timer_lock; + struct semaphore timer_enable_mask_lock; + spinlock_t timer_tick_lock; + atomic_t pending_event; + uint64_t timer_tick; + uint64_t timer_enable_mask; + wait_queue_head_t event_wait_que; + wait_queue_head_t timer_wait_que; + struct task_struct *event_task; + struct task_struct *timer_task; + + struct delayed_work init_work; + struct delayed_work event_init_work; + struct workqueue_struct *evt_wq; + struct srcu_notifier_head evt_nh[TCP_NOTIFY_IDX_NR]; + struct tcpc_managed_res *mr_head; + struct mutex mr_lock; + + /* For TCPC TypeC */ + uint8_t typec_state; + uint8_t typec_role; + uint8_t typec_role_new; + uint8_t typec_attach_old; + uint8_t typec_attach_new; + uint8_t typec_local_cc; + uint8_t typec_local_rp_level; + uint8_t typec_remote_cc[2]; + uint8_t typec_remote_rp_level; + uint8_t typec_wait_ps_change; + bool typec_polarity; + bool typec_drp_try_timeout; + bool typec_lpm; + bool typec_cable_only; + bool typec_power_ctrl; + bool typec_watchdog; + bool typec_reach_vsafe0v; + bool typec_is_attached_src; + + int typec_usb_sink_curr; + +#ifdef CONFIG_TYPEC_CAP_CUSTOM_HV + bool typec_during_custom_hv; +#endif /* CONFIG_TYPEC_CAP_CUSTOM_HV */ + + uint8_t typec_lpm_pull; + uint8_t typec_lpm_retry; + +#ifdef CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY + bool typec_wakeup_once; + bool typec_low_rp_duty_cntdown; +#endif /* CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY */ + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + uint8_t typec_legacy_cable; +#if TCPC_LEGACY_CABLE_SUSPECT_THD + uint8_t typec_legacy_cable_suspect; +#endif /* TCPC_LEGACY_CABLE_SUSPECT_THD */ + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + uint8_t typec_legacy_retry_wk; +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + uint8_t typec_during_role_swap; +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + +#ifdef CONFIG_TYPEC_CAP_AUTO_DISCHARGE +#ifdef CONFIG_TCPC_AUTO_DISCHARGE_IC + bool typec_auto_discharge; +#endif /* CONFIG_TCPC_AUTO_DISCHARGE_IC */ +#endif /* CONFIG_TYPEC_CAP_AUTO_DISCHARGE */ + +#ifdef CONFIG_TCPC_EXT_DISCHARGE + bool typec_ext_discharge; +#endif /* CONFIG_TCPC_EXT_DISCHARGE */ + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + uint8_t tcpc_vconn_supply; +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + +#ifdef CONFIG_TCPC_SOURCE_VCONN + bool tcpc_source_vconn; +#endif /* CONFIG_TCPC_SOURCE_VCONN */ + + uint32_t tcpc_flags; + +#ifdef CONFIG_DUAL_ROLE_USB_INTF + struct dual_role_phy_instance *dr_usb; + uint8_t dual_role_supported_modes; + uint8_t dual_role_mode; + uint8_t dual_role_pr; + uint8_t dual_role_dr; + uint8_t dual_role_vconn; +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + +#ifdef CONFIG_USB_POWER_DELIVERY + /* Event */ + uint8_t pd_event_count; + uint8_t pd_event_head_index; + uint8_t pd_msg_buffer_allocated; + + uint8_t pd_last_vdm_msg_id; + bool pd_pending_vdm_event; + bool pd_pending_vdm_reset; + bool pd_pending_vdm_good_crc; + bool pd_pending_vdm_discard; + bool pd_pending_vdm_attention; + bool pd_postpone_vdm_timeout; + + struct pd_msg pd_last_vdm_msg; + struct pd_msg pd_attention_vdm_msg; + struct pd_event pd_vdm_event; + + struct pd_msg pd_msg_buffer[PD_MSG_BUF_SIZE]; + struct pd_event pd_event_ring_buffer[PD_EVENT_BUF_SIZE]; + + uint8_t tcp_event_count; + uint8_t tcp_event_head_index; + struct tcp_dpm_event tcp_event_ring_buffer[TCP_EVENT_BUF_SIZE]; + + bool pd_pe_running; + bool pd_wait_pe_idle; + bool pd_hard_reset_event_pending; + bool pd_wait_hard_reset_complete; + bool pd_wait_pr_swap_complete; + bool pd_ping_event_pending; + uint8_t pd_bist_mode; + uint8_t pd_transmit_state; + uint8_t pd_wait_vbus_once; + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE + bool pd_during_direct_charge; +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + bool pd_discard_pending; +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + +#ifdef CONFIG_TYPEC_CAP_FORCE_DISCHARGE +#ifdef CONFIG_TCPC_FORCE_DISCHARGE_IC + bool pd_force_discharge; +#endif /* CONFIG_TCPC_FORCE_DISCHARGE_IC */ +#endif /* CONFIG_TYPEC_CAP_FORCE_DISCHARGE */ + +#ifdef CONFIG_USB_PD_REV30 + uint8_t pd_retry_count; +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_DISABLE_PE + bool disable_pe; /* typec only */ +#endif /* CONFIG_USB_PD_DISABLE_PE */ + + struct pd_port pd_port; +#ifdef CONFIG_USB_PD_REV30 + struct notifier_block bat_nb; + struct delayed_work bat_update_work; + struct power_supply *bat_psy; + uint8_t charging_status; + int bat_soc; +#endif /* CONFIG_USB_PD_REV30 */ +#ifdef CONFIG_USB_PD_WAIT_BC12 + uint8_t pd_wait_bc12_count; + struct power_supply *usb_psy; +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + u8 vbus_level:2; + bool vbus_safe0v; + bool vbus_present; + u8 irq_enabled:1; + u8 pd_inited_flag:1; + + /* TypeC Shield Protection */ +#ifdef CONFIG_WATER_DETECTION + int usbid_calib; +#endif /* CONFIG_WATER_DETECTION */ +#ifdef CONFIG_CABLE_TYPE_DETECTION + enum tcpc_cable_type typec_cable_type; +#endif /* CONFIG_CABLE_TYPE_DETECTION */ +}; + +#define to_tcpc_device(obj) container_of(obj, struct tcpc_device, dev) + +#ifdef CONFIG_USB_POWER_DELIVERY +static inline uint8_t pd_get_rev(struct pd_port *pd_port, uint8_t sop_type) +{ + uint8_t pd_rev = PD_REV20; +#ifdef CONFIG_USB_PD_REV30_SYNC_SPEC_REV + struct pe_data *pe_data = &pd_port->pe_data; + struct tcpc_device *tcpc = pd_port->tcpc; + + if (sop_type == TCPC_TX_SOP) { + pd_rev = pd_port->pd_revision[0]; + } else { + if (pe_data->explicit_contract || pe_data->cable_rev_discovered) + pd_rev = pd_port->pd_revision[1]; + else if (tcpc->tcpc_flags & TCPC_FLAGS_PD_REV30) + pd_rev = PD_REV30; + } +#endif /* CONFIG_USB_PD_REV30_SYNC_SPEC_REV */ + + return pd_rev; +} + +static inline bool pd_check_rev30(struct pd_port *pd_port) +{ + return pd_get_rev(pd_port, TCPC_TX_SOP) >= PD_REV30; +} +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_PD_DBG_INFO +#define __RT_DBG_INFO pd_dbg_info +#else +#define __RT_DBG_INFO pr_info +#endif /* CONFIG_PD_DBG_INFO */ + +#ifdef CONFIG_TCPC_LOG_WITH_PORT_NAME +#define RT_DBG_INFO(format, args...) __RT_DBG_INFO(format, \ + tcpc->desc.name, ##args) +#else +#define RT_DBG_INFO(format, args...) __RT_DBG_INFO(format, ##args) +#endif /* CONFIG_TCPC_LOG_WITH_PORT_NAME */ + +#if TYPEC_DBG_ENABLE +#define TYPEC_DBG(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "TYPEC:" format, ##args) +#else +#define TYPEC_DBG(format, args...) +#endif /* TYPEC_DBG_ENABLE */ + +#if TYPEC_INFO_ENABLE +#define TYPEC_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "TYPEC:" format, ##args) +#else +#define TYPEC_INFO(format, args...) +#endif /* TYPEC_INFO_ENABLE */ + +#if TYPEC_INFO2_ENABLE +#define TYPEC_INFO2(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "TYPEC:" format, ##args) +#else +#define TYPEC_INFO2(format, args...) +#endif /* TYPEC_INFO_ENABLE */ + +#if TCPC_INFO_ENABLE +#define TCPC_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "TCPC:" format, ##args) +#else +#define TCPC_INFO(foramt, args...) +#endif /* TCPC_INFO_ENABLE */ + +#if TCPC_DBG_ENABLE +#define TCPC_DBG(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "TCPC:" format, ##args) +#else +#define TCPC_DBG(format, args...) +#endif /* TCPC_DBG_ENABLE */ + +#if TCPC_DBG2_ENABLE +#define TCPC_DBG2(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "TCPC:" format, ##args) +#else +#define TCPC_DBG2(format, args...) +#endif /* TCPC_DBG2_ENABLE */ + +#define TCPC_ERR(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "TCPC-ERR:" format, ##args) + +#define DP_ERR(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "DP-ERR:" format, ##args) + +#if DPM_INFO_ENABLE +#define DPM_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "DPM:" format, ##args) +#else +#define DPM_INFO(format, args...) +#endif /* DPM_DBG_INFO */ + +#if DPM_INFO2_ENABLE +#define DPM_INFO2(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "DPM:" format, ##args) +#else +#define DPM_INFO2(format, args...) +#endif /* DPM_DBG_INFO */ + +#if DPM_DBG_ENABLE +#define DPM_DBG(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "DPM:" format, ##args) +#else +#define DPM_DBG(format, args...) +#endif /* DPM_DBG_ENABLE */ + +#if PD_ERR_ENABLE +#define PD_ERR(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "PD-ERR:" format, ##args) +#else +#define PD_ERR(format, args...) +#endif /* PD_ERR_ENABLE */ + +#if PE_INFO_ENABLE +#define PE_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "PE:" format, ##args) +#else +#define PE_INFO(format, args...) +#endif /* PE_INFO_ENABLE */ + +#if PE_EVENT_DBG_ENABLE +#define PE_EVT_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "PE-EVT:" format, ##args) +#else +#define PE_EVT_INFO(format, args...) +#endif /* PE_EVENT_DBG_ENABLE */ + +#if PE_DBG_ENABLE +#define PE_DBG(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "PE:" format, ##args) +#else +#define PE_DBG(format, args...) +#endif /* PE_DBG_ENABLE */ + +#if PE_STATE_INFO_ENABLE +#define PE_STATE_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "PE:" format, ##args) +#else +#define PE_STATE_INFO(format, args...) +#endif /* PE_STATE_IFNO_ENABLE */ + +#if DP_INFO_ENABLE +#define DP_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "DP:" format, ##args) +#else +#define DP_INFO(format, args...) +#endif /* DP_INFO_ENABLE */ + +#if DP_DBG_ENABLE +#define DP_DBG(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "DP:" format, ##args) +#else +#define DP_DBG(format, args...) +#endif /* DP_DBG_ENABLE */ + +#if UVDM_INFO_ENABLE +#define UVDM_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "UVDM:" format, ##args) +#else +#define UVDM_INFO(format, args...) +#endif + +#if TCPM_DBG_ENABLE +#define TCPM_DBG(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "TCPM:" format, ##args) +#else +#define TCPM_DBG(format, args...) +#endif + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC + +#if DC_INFO_ENABLE +#define DC_INFO(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "DC> " format, ##args) +#else +#define DC_INFO(format, args...) +#endif + +#if DC_DBG_ENABLE +#define DC_DBG(format, args...) \ + RT_DBG_INFO(CONFIG_TCPC_DBG_PRESTR "DC> " format, ##args) +#else +#define DC_DBG(format, args...) +#endif + +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +#endif /* #ifndef __LINUX_RT_TCPCI_CORE_H */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/tcpci_event.h b/drivers/usb/typec/mmi_tcpc/inc/tcpci_event.h new file mode 100644 index 000000000000..9b8c22df91a2 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/tcpci_event.h @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef TCPC_EVENT_BUF_H_INCLUDED +#define TCPC_EVENT_BUF_H_INCLUDED + +#include "tcpci_timer.h" +#include "tcpm.h" + + +#define PD_MSG_BUF_SIZE (4*2) +#define PD_EVENT_BUF_SIZE (8*2) +#define TCP_EVENT_BUF_SIZE (2*2) + +struct tcpc_device; + +struct pd_msg { + uint8_t frame_type; + uint16_t msg_hdr; + uint32_t payload[7]; +}; + +struct pd_event { + uint8_t event_type; + uint8_t msg; + uint8_t msg_sec; + struct pd_msg *pd_msg; +}; + +struct pd_msg *pd_alloc_msg(struct tcpc_device *tcpc); +void pd_free_msg(struct tcpc_device *tcpc, struct pd_msg *pd_msg); + +bool pd_get_event(struct tcpc_device *tcpc, struct pd_event *pd_event); +bool pd_put_event(struct tcpc_device *tcpc, + const struct pd_event *pd_event, bool from_port_partner); +void pd_free_event(struct tcpc_device *tcpc, struct pd_event *pd_event); + +bool pd_get_vdm_event(struct tcpc_device *tcpc, struct pd_event *pd_event); +bool pd_put_vdm_event(struct tcpc_device *tcpc, + struct pd_event *pd_event, bool from_port_partner); + +bool pd_put_last_vdm_event(struct tcpc_device *tcpc); + +bool pd_get_deferred_tcp_event( + struct tcpc_device *tcpc, struct tcp_dpm_event *tcp_event); +bool pd_put_deferred_tcp_event( + struct tcpc_device *tcpc, const struct tcp_dpm_event *tcp_event); + +extern int tcpci_event_init(struct tcpc_device *tcpc); +extern int tcpci_event_deinit(struct tcpc_device *tcpc); +extern void pd_event_buf_reset(struct tcpc_device *tcpc); + +bool __pd_put_cc_attached_event(struct tcpc_device *tcpc, uint8_t type); +bool pd_put_cc_attached_event(struct tcpc_device *tcpc, uint8_t type); +void pd_put_cc_detached_event(struct tcpc_device *tcpc); +void pd_put_recv_hard_reset_event(struct tcpc_device *tcpc); +void pd_put_sent_hard_reset_event(struct tcpc_device *tcpc); +bool pd_put_pd_msg_event(struct tcpc_device *tcpc, struct pd_msg *pd_msg); +void pd_put_hard_reset_completed_event(struct tcpc_device *tcpc); +void pd_put_vbus_changed_event(struct tcpc_device *tcpc, bool from_ic); +void pd_put_vbus_safe0v_event(struct tcpc_device *tcpc); +void pd_put_vbus_stable_event(struct tcpc_device *tcpc); +void pd_put_vbus_present_event(struct tcpc_device *tcpc); + +enum pd_event_type { + PD_EVT_PD_MSG = 0, /* either ctrl msg or data msg */ + PD_EVT_CTRL_MSG, + PD_EVT_DATA_MSG, + +#ifdef CONFIG_USB_PD_REV30 + PD_EVT_EXT_MSG, +#endif /* CONFIG_USB_PD_REV30 */ + + PD_EVT_PD_MSG_END, + + PD_EVT_DPM_MSG = PD_EVT_PD_MSG_END, + PD_EVT_HW_MSG, + PD_EVT_PE_MSG, + PD_EVT_TIMER_MSG, + + PD_EVT_TCP_MSG, +}; + +enum pd_msg_type { +/* Control Message type */ + /* 0 Reserved */ + PD_CTRL_GOOD_CRC = 1, + PD_CTRL_GOTO_MIN = 2, + PD_CTRL_ACCEPT = 3, + PD_CTRL_REJECT = 4, + PD_CTRL_PING = 5, + PD_CTRL_PS_RDY = 6, + PD_CTRL_GET_SOURCE_CAP = 7, + PD_CTRL_GET_SINK_CAP = 8, + PD_CTRL_DR_SWAP = 9, + PD_CTRL_PR_SWAP = 10, + PD_CTRL_VCONN_SWAP = 11, + PD_CTRL_WAIT = 12, + PD_CTRL_SOFT_RESET = 13, + /* 14-15 Reserved */ + PD_CTRL_PD30_START = 0x10 + 0, +#ifdef CONFIG_USB_PD_REV30 + PD_CTRL_NOT_SUPPORTED = 0x10 + 0, + PD_CTRL_GET_SOURCE_CAP_EXT = 0x10 + 1, + PD_CTRL_GET_STATUS = 0x10 + 2, + PD_CTRL_FR_SWAP = 0x10 + 3, + PD_CTRL_GET_PPS_STATUS = 0x10 + 4, + PD_CTRL_GET_COUNTRY_CODE = 0x10 + 5, +#endif /* CONFIG_USB_PD_REV30 */ + /* 22-31 Reserved */ + PD_CTRL_MSG_NR, +/* Data message type */ + /* 0 Reserved */ + PD_DATA_SOURCE_CAP = 1, + PD_DATA_REQUEST = 2, + PD_DATA_BIST = 3, + PD_DATA_SINK_CAP = 4, + PD_DATA_PD30_START = 5, +#ifdef CONFIG_USB_PD_REV30 + PD_DATA_BAT_STATUS = 5, + PD_DATA_ALERT = 6, + PD_DATA_GET_COUNTRY_INFO = 7, +#endif /* CONFIG_USB_PD_REV30 */ + /* 7-14 Reserved */ + PD_DATA_VENDOR_DEF = 15, + PD_DATA_MSG_NR, +#ifdef CONFIG_USB_PD_REV30 +/* Extended message type */ + /* 0 Reserved */ + PD_EXT_SOURCE_CAP_EXT = 1, + PD_EXT_STATUS = 2, + PD_EXT_GET_BAT_CAP = 3, + PD_EXT_GET_BAT_STATUS = 4, + PD_EXT_BAT_CAP = 5, + PD_EXT_GET_MFR_INFO = 6, + PD_EXT_MFR_INFO = 7, + PD_EXT_SEC_REQUEST = 8, + PD_EXT_SEC_RESPONSE = 9, + PD_EXT_FW_UPDATE_REQUEST = 10, + PD_EXT_FW_UPDATE_RESPONSE = 11, + PD_EXT_PPS_STATUS = 12, + PD_EXT_COUNTRY_INFO = 13, + PD_EXT_COUNTRY_CODES = 14, + /* 15 Reserved */ + PD_EXT_MSG_NR, +#endif /* CONFIG_USB_PD_REV30 */ +/* HW Message type */ + PD_HW_CC_DETACHED = 0, + PD_HW_CC_ATTACHED, + PD_HW_RECV_HARD_RESET, + PD_HW_VBUS_PRESENT, + PD_HW_VBUS_ABSENT, + PD_HW_VBUS_SAFE0V, + PD_HW_VBUS_STABLE, + PD_HW_TX_FAILED, /* no good crc or discard */ + PD_HW_TX_DISCARD, /* discard vdm msg */ + PD_HW_RETRY_VDM, /* discard vdm msg (retry) */ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + PD_HW_SINK_TX_CHANGE, +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + PD_HW_MSG_NR, +/* PE Message type*/ + PD_PE_RESET_PRL_COMPLETED = 0, + PD_PE_POWER_ROLE_AT_DEFAULT, + PD_PE_HARD_RESET_COMPLETED, + PD_PE_IDLE, + PD_PE_VDM_RESET, + PD_PE_VDM_NOT_SUPPORT, + PD_PE_MSG_NR, +/* DPM Message type */ + PD_DPM_NOTIFIED = 0, + PD_DPM_ACK = PD_DPM_NOTIFIED, + PD_DPM_NAK, + PD_DPM_CAP_CHANGED, + PD_DPM_NOT_SUPPORT, + PD_DPM_MSG_NR, +}; + +enum pd_dpm_notify_type { + PD_DPM_NOTIFY_OK = 0, + PD_DPM_NOTIFY_CAP_MISMATCH, +}; + +enum pd_dpm_nak_type { + PD_DPM_NAK_REJECT = 0, + PD_DPM_NAK_WAIT = 1, +}; + +enum pd_tcp_sec_msg_type { + PD_TCP_FROM_TCPM = 0, + PD_TCP_FROM_PE = 1, +}; + +enum pd_tx_transmit_state { + PD_TX_STATE_GOOD_CRC = 0, + PD_TX_STATE_NO_GOOD_CRC, + PD_TX_STATE_DISCARD, + PD_TX_STATE_HARD_RESET, + PD_TX_STATE_NO_RESPONSE, + + PD_TX_STATE_WAIT, + PD_TX_STATE_WAIT_CRC_VDM = PD_TX_STATE_WAIT, + PD_TX_STATE_WAIT_CRC_PD, + PD_TX_STATE_WAIT_HARD_RESET, +}; + +static inline bool pd_event_msg_match(struct pd_event *pd_event, + uint8_t type, uint8_t msg) +{ + if (pd_event->event_type != type) + return false; + + return pd_event->msg == msg; +} + +static inline bool pd_event_ctrl_msg_match( + struct pd_event *pd_event, uint8_t msg) +{ + return pd_event_msg_match(pd_event, PD_EVT_CTRL_MSG, msg); +} + +static inline bool pd_event_data_msg_match( + struct pd_event *pd_event, uint8_t msg) +{ + return pd_event_msg_match(pd_event, PD_EVT_DATA_MSG, msg); +} + +static inline bool pd_event_hw_msg_match(struct pd_event *pd_event, uint8_t msg) +{ + return pd_event_msg_match(pd_event, PD_EVT_HW_MSG, msg); +} + +static inline bool pd_event_pe_msg_match(struct pd_event *pd_event, uint8_t msg) +{ + return pd_event_msg_match(pd_event, PD_EVT_PE_MSG, msg); +} + +static inline bool pd_event_timer_msg_match( + struct pd_event *pd_event, uint8_t msg) +{ + return pd_event_msg_match(pd_event, PD_EVT_TIMER_MSG, msg); +} + +#ifdef CONFIG_USB_PD_REV30 + +static inline bool pd_event_ext_msg_match( + struct pd_event *pd_event, uint8_t msg) +{ + return pd_event_msg_match(pd_event, PD_EVT_EXT_MSG, msg); +} + +#endif /* CONFIG_USB_PD_REV30 */ + +#endif /* TCPC_EVENT_BUF_H_INCLUDED */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/tcpci_timer.h b/drivers/usb/typec/mmi_tcpc/inc/tcpci_timer.h new file mode 100644 index 000000000000..901919bdbdc8 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/tcpci_timer.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef TCPC_TIMER_H_INCLUDED +#define TCPC_TIMER_H_INCLUDED + +#include +#include "tcpci_config.h" + +struct tcpc_device; +enum { +#ifdef CONFIG_USB_POWER_DELIVERY + PD_TIMER_DISCOVER_ID = 0, + PD_TIMER_BIST_CONT_MODE, + PD_TIMER_HARD_RESET_COMPLETE, + PD_TIMER_NO_RESPONSE, + PD_TIMER_PS_HARD_RESET, + PD_TIMER_PS_SOURCE_OFF, + PD_TIMER_PS_SOURCE_ON, + PD_TIMER_PS_TRANSITION, + PD_TIMER_SENDER_RESPONSE, + PD_TIMER_SINK_REQUEST, + PD_TIMER_SINK_WAIT_CAP, + PD_TIMER_SOURCE_CAPABILITY, + PD_TIMER_SOURCE_START, + PD_TIMER_VCONN_ON, +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY + PD_TIMER_VCONN_STABLE, +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + PD_TIMER_VDM_MODE_ENTRY, + PD_TIMER_VDM_MODE_EXIT, + PD_TIMER_VDM_RESPONSE, + PD_TIMER_SOURCE_TRANSITION, + PD_TIMER_SRC_RECOVER, +#ifdef CONFIG_USB_PD_REV30 + PD_TIMER_CK_NOT_SUPPORTED, +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + PD_TIMER_SINK_TX, +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + PD_TIMER_SOURCE_PPS_TIMEOUT, +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ + +/* PD_TIMER (out of spec )*/ + PD_TIMER_VSAFE0V_DELAY, + PD_TIMER_VSAFE0V_TOUT, + PD_TIMER_DISCARD, + PD_TIMER_VBUS_STABLE, + PD_TIMER_VBUS_PRESENT, + PD_TIMER_UVDM_RESPONSE, + PD_TIMER_DFP_FLOW_DELAY, + PD_TIMER_UFP_FLOW_DELAY, + PD_TIMER_VCONN_READY, + PD_PE_VDM_POSTPONE, + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + PD_TIMER_DEFERRED_EVT, +#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP + PD_TIMER_SNK_FLOW_DELAY, +#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */ +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + + PD_TIMER_PE_IDLE_TOUT, + PD_PE_TIMER_END_ID, +#endif /* CONFIG_USB_POWER_DELIVERY */ + +/* TYPEC_RT_TIMER (out of spec )*/ +#ifdef CONFIG_USB_POWER_DELIVERY + TYPEC_RT_TIMER_START_ID = PD_PE_TIMER_END_ID, +#else + TYPEC_RT_TIMER_START_ID = 0, +#endif /* CONFIG_USB_POWER_DELIVERY */ + + TYPEC_RT_TIMER_SAFE0V_DELAY = TYPEC_RT_TIMER_START_ID, + TYPEC_RT_TIMER_SAFE0V_TOUT, + TYPEC_RT_TIMER_ROLE_SWAP_START, + TYPEC_RT_TIMER_ROLE_SWAP_STOP, + TYPEC_RT_TIMER_STATE_CHANGE, + TYPEC_RT_TIMER_NOT_LEGACY, + TYPEC_RT_TIMER_LEGACY_STABLE, + TYPEC_RT_TIMER_LEGACY_RECYCLE, + TYPEC_RT_TIMER_DISCHARGE, + TYPEC_RT_TIMER_LOW_POWER_MODE, + +#ifdef CONFIG_USB_POWER_DELIVERY + TYPEC_RT_TIMER_PE_IDLE, +#ifdef CONFIG_USB_PD_WAIT_BC12 + TYPEC_RT_TIMER_PD_WAIT_BC12, +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + TYPEC_TIMER_ERROR_RECOVERY, +/* TYPEC-TRY-TIMER */ + TYPEC_TRY_TIMER_START_ID, + TYPEC_TRY_TIMER_DRP_TRY = TYPEC_TRY_TIMER_START_ID, + TYPEC_TRY_TIMER_DRP_TRYWAIT, +/* TYPEC-DEBOUNCE-TIMER */ + TYPEC_TIMER_START_ID, + TYPEC_TIMER_CCDEBOUNCE = TYPEC_TIMER_START_ID, + TYPEC_TIMER_PDDEBOUNCE, +#ifdef CONFIG_COMPATIBLE_APPLE_TA + TYPEC_TIMER_APPLE_CC_OPEN, +#endif /* CONFIG_COMPATIBLE_APPLE_TA */ + TYPEC_TIMER_TRYCCDEBOUNCE, + TYPEC_TIMER_SRCDISCONNECT, + TYPEC_TIMER_DRP_SRC_TOGGLE, +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + TYPEC_TIMER_NORP_SRC, +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + PD_TIMER_NR, +}; + +extern int tcpci_timer_init(struct tcpc_device *tcpc); +extern int tcpci_timer_deinit(struct tcpc_device *tcpc); +extern void tcpc_restart_timer(struct tcpc_device *tcpc, uint32_t timer_id); +extern void tcpc_enable_timer(struct tcpc_device *tcpc, uint32_t timer_id); +extern void tcpc_enable_wakeup_timer(struct tcpc_device *tcpc, bool en); +extern void tcpc_disable_timer( + struct tcpc_device *tcpc, uint32_t timer_id); +extern void tcpc_reset_typec_try_timer(struct tcpc_device *tcpc); +extern void tcpc_reset_typec_debounce_timer(struct tcpc_device *tcpc); + +extern void tcpc_reset_pe_timer(struct tcpc_device *tcpc); +#endif /* TCPC_TIMER_H_INCLUDED */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/tcpci_typec.h b/drivers/usb/typec/mmi_tcpc/inc/tcpci_typec.h new file mode 100644 index 000000000000..97b9549838de --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/tcpci_typec.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_TCPCI_TYPEC_H +#define __LINUX_TCPCI_TYPEC_H +#include "tcpci.h" + +struct tcpc_device; +extern bool tcpc_typec_is_act_as_sink_role(struct tcpc_device *tcpc); + +/****************************************************************************** + * Call following function to trigger TYPEC Connection State Change + * + * 1. H/W -> CC/PS Change. + * 2. Timer -> CCDebounce or PDDebounce or others Timeout + * 3. Policy Engine -> PR_SWAP, Error_Recovery, PE_Idle + *****************************************************************************/ + +extern int tcpc_typec_enter_lpm_again(struct tcpc_device *tcpc); +extern int tcpc_typec_handle_cc_change(struct tcpc_device *tcpc); + +extern int tcpc_typec_handle_ps_change( + struct tcpc_device *tcpc, int vbus_level); + +extern int tcpc_typec_handle_timeout( + struct tcpc_device *tcpc, uint32_t timer_id); + +extern int tcpc_typec_handle_vsafe0v(struct tcpc_device *tcpc); + +extern int tcpc_typec_set_rp_level(struct tcpc_device *tcpc, uint8_t res); + +extern int tcpc_typec_error_recovery(struct tcpc_device *tcpc); + +extern int tcpc_typec_disable(struct tcpc_device *tcpc); +extern int tcpc_typec_enable(struct tcpc_device *tcpc); + +extern int tcpc_typec_change_role( + struct tcpc_device *tcpc, uint8_t typec_role, bool postpone); + +#ifdef CONFIG_USB_POWER_DELIVERY +extern int tcpc_typec_handle_pe_pr_swap(struct tcpc_device *tcpc); +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP +extern int tcpc_typec_swap_role(struct tcpc_device *tcpc); +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + +#ifdef CONFIG_WATER_DETECTION +extern int tcpc_typec_handle_wd(struct tcpc_device *tcpc, bool wd); +#endif /* CONFIG_WATER_DETECTION */ + +#ifdef CONFIG_CABLE_TYPE_DETECTION +extern int tcpc_typec_handle_ctd(struct tcpc_device *tcpc, + enum tcpc_cable_type cable_type); +#endif /* CONFIG_CABLE_TYPEC_DETECTION */ + +#define typec_get_cc1() \ + tcpc->typec_remote_cc[0] +#define typec_get_cc2() \ + tcpc->typec_remote_cc[1] +#define typec_get_cc_res() \ + (tcpc->typec_polarity ? typec_get_cc2() : typec_get_cc1()) + +#endif /* #ifndef __LINUX_TCPCI_TYPEC_H */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/tcpm.h b/drivers/usb/typec/mmi_tcpc/inc/tcpm.h new file mode 100644 index 000000000000..a878c20acb2e --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/tcpm.h @@ -0,0 +1,1921 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef TCPM_H_ +#define TCPM_H_ + +#include +#include + +#include "tcpci_config.h" +#include "tcpm_pd.h" + +struct tcpc_device; + +/* + * Type-C Port Notify Chain + */ + +enum typec_attach_type { + TYPEC_UNATTACHED = 0, + TYPEC_ATTACHED_SNK, + TYPEC_ATTACHED_SRC, + TYPEC_ATTACHED_AUDIO, + TYPEC_ATTACHED_DEBUG, /* Rd, Rd */ + +/* CONFIG_TYPEC_CAP_DBGACC_SNK */ + TYPEC_ATTACHED_DBGACC_SNK, /* Rp, Rp */ + +/* CONFIG_TYPEC_CAP_CUSTOM_SRC */ + TYPEC_ATTACHED_CUSTOM_SRC, /* Same Rp */ + +/* CONFIG_TYPEC_CAP_NORP_SRC */ + TYPEC_ATTACHED_NORP_SRC, /* No Rp */ +}; + +enum pd_connect_result { + PD_CONNECT_NONE = 0, + PD_CONNECT_TYPEC_ONLY_SNK_DFT, + PD_CONNECT_TYPEC_ONLY_SNK, + PD_CONNECT_TYPEC_ONLY_SRC, + PD_CONNECT_PE_READY_SNK, + PD_CONNECT_PE_READY_SRC, + PD_CONNECT_PE_READY_SNK_PD30, + PD_CONNECT_PE_READY_SRC_PD30, + PD_CONNECT_PE_READY_SNK_APDO, + PD_CONNECT_HARD_RESET, + +/* CONFIG_USB_PD_CUSTOM_DBGACC */ + PD_CONNECT_PE_READY_DBGACC_UFP, + PD_CONNECT_PE_READY_DBGACC_DFP, +}; + +enum tcpc_vconn_supply_mode { + /* Never provide vconn even in TYPE-C state (reject swap to On) */ + TCPC_VCONN_SUPPLY_NEVER = 0, + + /* Always provide vconn */ + TCPC_VCONN_SUPPLY_ALWAYS, + + /* Always provide vconn only if we detect Ra, otherwise startup only */ + TCPC_VCONN_SUPPLY_EMARK_ONLY, + + /* Only provide vconn during DPM initial (aginst spec) */ + TCPC_VCONN_SUPPLY_STARTUP, + + TCPC_VCONN_SUPPLY_NR, +}; + +/* Power role */ +#define PD_ROLE_SINK 0 +#define PD_ROLE_SOURCE 1 + +/* Data role */ +#define PD_ROLE_UFP 0 +#define PD_ROLE_DFP 1 + +/* Vconn role */ +#define PD_ROLE_VCONN_OFF 0 +#define PD_ROLE_VCONN_ON 1 +#define PD_ROLE_VCONN_DYNAMIC_OFF 2 +#define PD_ROLE_VCONN_DYNAMIC_ON 3 + +enum { + TCP_NOTIFY_IDX_MODE = 0, + TCP_NOTIFY_IDX_VBUS, + TCP_NOTIFY_IDX_USB, + TCP_NOTIFY_IDX_MISC, + TCP_NOTIFY_IDX_NR, +}; + +#define TCP_NOTIFY_TYPE_MODE (1 << TCP_NOTIFY_IDX_MODE) +#define TCP_NOTIFY_TYPE_VBUS (1 << TCP_NOTIFY_IDX_VBUS) +#define TCP_NOTIFY_TYPE_USB (1 << TCP_NOTIFY_IDX_USB) +#define TCP_NOTIFY_TYPE_MISC (1 << TCP_NOTIFY_IDX_MISC) + +#define TCP_NOTIFY_TYPE_ALL ((1 << TCP_NOTIFY_IDX_NR) - 1) + +enum { + /* TCP_NOTIFY_TYPE_MODE */ + TCP_NOTIFY_ENTER_MODE, + TCP_NOTIFY_MODE_START = TCP_NOTIFY_ENTER_MODE, + TCP_NOTIFY_EXIT_MODE, + TCP_NOTIFY_AMA_DP_STATE, + TCP_NOTIFY_AMA_DP_ATTENTION, + TCP_NOTIFY_AMA_DP_HPD_STATE, + TCP_NOTIFY_DC_EN_UNLOCK, + TCP_NOTIFY_UVDM, + TCP_NOTIFY_MODE_END = TCP_NOTIFY_UVDM, + + /* TCP_NOTIFY_TYPE_VBUS */ + TCP_NOTIFY_DIS_VBUS_CTRL, + TCP_NOTIFY_VBUS_START = TCP_NOTIFY_DIS_VBUS_CTRL, + TCP_NOTIFY_SOURCE_VCONN, + TCP_NOTIFY_SOURCE_VBUS, + TCP_NOTIFY_SINK_VBUS, + TCP_NOTIFY_EXT_DISCHARGE, + TCP_NOTIFY_ATTACHWAIT_SNK, + TCP_NOTIFY_ATTACHWAIT_SRC, + TCP_NOTIFY_VBUS_END = TCP_NOTIFY_ATTACHWAIT_SRC, + + /* TCP_NOTIFY_TYPE_USB */ + TCP_NOTIFY_TYPEC_STATE, + TCP_NOTIFY_USB_START = TCP_NOTIFY_TYPEC_STATE, + TCP_NOTIFY_PD_STATE, + TCP_NOTIFY_USB_END = TCP_NOTIFY_PD_STATE, + + /* TCP_NOTIFY_TYPE_MISC */ + TCP_NOTIFY_PR_SWAP, + TCP_NOTIFY_MISC_START = TCP_NOTIFY_PR_SWAP, + TCP_NOTIFY_DR_SWAP, + TCP_NOTIFY_VCONN_SWAP, + TCP_NOTIFY_HARD_RESET_STATE, + TCP_NOTIFY_ALERT, + TCP_NOTIFY_STATUS, + TCP_NOTIFY_REQUEST_BAT_INFO, + TCP_NOTIFY_WD_STATUS, + TCP_NOTIFY_CABLE_TYPE, + TCP_NOTIFY_MISC_END = TCP_NOTIFY_CABLE_TYPE, +}; + +struct tcp_ny_pd_state { + uint8_t connected; +}; + +struct tcp_ny_swap_state { + uint8_t new_role; +}; + +struct tcp_ny_enable_state { + bool en; +}; + +struct tcp_ny_typec_state { + uint8_t rp_level; + uint8_t local_rp_level; + uint8_t polarity; + uint8_t old_state; + uint8_t new_state; +}; + +enum { + TCP_VBUS_CTRL_REMOVE = 0, + TCP_VBUS_CTRL_TYPEC = 1, + TCP_VBUS_CTRL_PD = 2, + + TCP_VBUS_CTRL_HRESET = TCP_VBUS_CTRL_PD, + TCP_VBUS_CTRL_PR_SWAP = 3, + TCP_VBUS_CTRL_REQUEST = 4, + TCP_VBUS_CTRL_STANDBY = 5, + TCP_VBUS_CTRL_STANDBY_UP = 6, + TCP_VBUS_CTRL_STANDBY_DOWN = 7, + + TCP_VBUS_CTRL_PD_DETECT = (1 << 7), + + TCP_VBUS_CTRL_PD_HRESET = + TCP_VBUS_CTRL_HRESET | TCP_VBUS_CTRL_PD_DETECT, + + TCP_VBUS_CTRL_PD_PR_SWAP = + TCP_VBUS_CTRL_PR_SWAP | TCP_VBUS_CTRL_PD_DETECT, + + TCP_VBUS_CTRL_PD_REQUEST = + TCP_VBUS_CTRL_REQUEST | TCP_VBUS_CTRL_PD_DETECT, + + TCP_VBUS_CTRL_PD_STANDBY = + TCP_VBUS_CTRL_STANDBY | TCP_VBUS_CTRL_PD_DETECT, + + TCP_VBUS_CTRL_PD_STANDBY_UP = + TCP_VBUS_CTRL_STANDBY_UP | TCP_VBUS_CTRL_PD_DETECT, + + TCP_VBUS_CTRL_PD_STANDBY_DOWN = + TCP_VBUS_CTRL_STANDBY_DOWN | TCP_VBUS_CTRL_PD_DETECT, +}; + +struct tcp_ny_vbus_state { + int mv; + int ma; + uint8_t type; +}; + +struct tcp_ny_mode_ctrl { + uint16_t svid; + uint8_t ops; + uint32_t mode; +}; + +enum { + SW_USB = 0, + SW_DFP_D, + SW_UFP_D, +}; + +struct tcp_ny_ama_dp_state { + uint8_t sel_config; + uint8_t signal; + uint8_t pin_assignment; + uint8_t polarity; + uint8_t active; +}; + +enum { + TCP_DP_UFP_U_MASK = 0x7C, + TCP_DP_UFP_U_POWER_LOW = 1 << 2, + TCP_DP_UFP_U_ENABLED = 1 << 3, + TCP_DP_UFP_U_MF_PREFER = 1 << 4, + TCP_DP_UFP_U_USB_CONFIG = 1 << 5, + TCP_DP_UFP_U_EXIT_MODE = 1 << 6, +}; + +struct tcp_ny_ama_dp_attention { + uint8_t state; +}; + +struct tcp_ny_ama_dp_hpd_state { + uint8_t irq; + uint8_t state; +}; + +struct tcp_ny_uvdm { + bool ack; + uint8_t uvdm_cnt; + uint16_t uvdm_svid; + uint32_t *uvdm_data; +}; + +/* + * TCP_NOTIFY_HARD_RESET_STATE + * + * Please don't expect that every signal will have a corresponding result. + * The signal can be generated multiple times before receiving a result. + */ + +enum { + /* HardReset finished because recv GoodCRC or TYPE-C only */ + TCP_HRESET_RESULT_DONE = 0, + + /* HardReset failed because detach or error recovery */ + TCP_HRESET_RESULT_FAIL, + + /* HardReset signal from Local Policy Engine */ + TCP_HRESET_SIGNAL_SEND, + + /* HardReset signal from Port Partner */ + TCP_HRESET_SIGNAL_RECV, +}; + +struct tcp_ny_hard_reset_state { + uint8_t state; +}; + +struct tcp_ny_alert { + uint32_t ado; +}; + +struct tcp_ny_status { + const struct pd_status *sdb; +}; + +struct tcp_ny_request_bat { + enum pd_battery_reference ref; +}; + +struct tcp_ny_wd_status { + bool water_detected; +}; + +enum tcpc_cable_type { + TCPC_CABLE_TYPE_NONE = 0, + TCPC_CABLE_TYPE_A2C, + TCPC_CABLE_TYPE_C2C, + TCPC_CABLE_TYPE_MAX, +}; + +struct tcp_ny_cable_type { + enum tcpc_cable_type type; +}; + +struct tcp_notify { + union { + struct tcp_ny_enable_state en_state; + struct tcp_ny_vbus_state vbus_state; + struct tcp_ny_typec_state typec_state; + struct tcp_ny_swap_state swap_state; + struct tcp_ny_pd_state pd_state; + struct tcp_ny_mode_ctrl mode_ctrl; + struct tcp_ny_ama_dp_state ama_dp_state; + struct tcp_ny_ama_dp_attention ama_dp_attention; + struct tcp_ny_ama_dp_hpd_state ama_dp_hpd_state; + struct tcp_ny_uvdm uvdm_msg; + struct tcp_ny_hard_reset_state hreset_state; + struct tcp_ny_alert alert_msg; + struct tcp_ny_status status_msg; + struct tcp_ny_request_bat request_bat; + struct tcp_ny_wd_status wd_status; + struct tcp_ny_cable_type cable_type; + }; +}; + +/* + * Type-C Port Control I/F + */ + +enum tcpm_error_list { + TCPM_SUCCESS = 0, + TCPM_ERROR_UNKNOWN = -1, + TCPM_ERROR_UNATTACHED = -2, + TCPM_ERROR_PARAMETER = -3, + TCPM_ERROR_PUT_EVENT = -4, + TCPM_ERROR_NO_SUPPORT = -5, + TCPM_ERROR_NO_PD_CONNECTED = -6, + TCPM_ERROR_NO_POWER_CABLE = -7, + TCPM_ERROR_NO_PARTNER_INFORM = -8, + TCPM_ERROR_NO_SOURCE_CAP = -9, + TCPM_ERROR_NO_SINK_CAP = -10, + TCPM_ERROR_NOT_DRP_ROLE = -11, + TCPM_ERROR_DURING_ROLE_SWAP = -12, + TCPM_ERROR_NO_EXPLICIT_CONTRACT = -13, + TCPM_ERROR_ERROR_RECOVERY = -14, + TCPM_ERROR_NOT_FOUND = -15, + TCPM_ERROR_INVALID_POLICY = -16, + TCPM_ERROR_EXPECT_CB2 = -17, + TCPM_ERROR_POWER_ROLE = -18, + TCPM_ERROR_PE_NOT_READY = -19, + TCPM_ERROR_REPEAT_POLICY = -20, + TCPM_ERROR_CUSTOM_SRC = -21, + TCPM_ERROR_NO_IMPLEMENT = -22, + TCPM_ALERT = -23, +}; + +/* Inquire TCPM status */ + +enum tcpc_cc_voltage_status { + TYPEC_CC_VOLT_OPEN = 0, + TYPEC_CC_VOLT_RA = 1, + TYPEC_CC_VOLT_RD = 2, + + TYPEC_CC_VOLT_SNK_DFT = 5, + TYPEC_CC_VOLT_SNK_1_5 = 6, + TYPEC_CC_VOLT_SNK_3_0 = 7, + + TYPEC_CC_DRP_TOGGLING = 15, +}; + +enum tcpm_vbus_level { +#ifdef CONFIG_TCPC_VSAFE0V_DETECT + TCPC_VBUS_SAFE0V = 0, /* < 0.8V */ + TCPC_VBUS_INVALID, /* > 0.8V */ + TCPC_VBUS_VALID, /* > 4.5V */ +#else + TCPC_VBUS_INVALID = 0, + TCPC_VBUS_VALID, +#endif /* CONFIG_TCPC_VSAFE0V_DETECT */ +}; + +enum typec_role_defination { + TYPEC_ROLE_UNKNOWN = 0, + TYPEC_ROLE_SNK, + TYPEC_ROLE_SRC, + TYPEC_ROLE_DRP, + TYPEC_ROLE_TRY_SRC, + TYPEC_ROLE_TRY_SNK, + TYPEC_ROLE_NR, +}; + +enum pd_cable_current_limit { + PD_CABLE_CURR_UNKNOWN = 0, + PD_CABLE_CURR_1A5 = 1, + PD_CABLE_CURR_3A = 2, + PD_CABLE_CURR_5A = 3, +}; + +/* DPM Flags */ + +#define DPM_FLAGS_PARTNER_DR_POWER (1<<0) +#define DPM_FLAGS_PARTNER_DR_DATA (1<<1) +#define DPM_FLAGS_PARTNER_EXTPOWER (1<<2) +#define DPM_FLAGS_PARTNER_USB_COMM (1<<3) +#define DPM_FLAGS_PARTNER_USB_SUSPEND (1<<4) +#define DPM_FLAGS_PARTNER_HIGH_CAP (1<<5) + +#define DPM_FLAGS_PARTNER_MISMATCH (1<<7) +#define DPM_FLAGS_PARTNER_GIVE_BACK (1<<8) +#define DPM_FLAGS_PARTNER_NO_SUSPEND (1<<9) + +#define DPM_FLAGS_RESET_PARTNER_MASK \ + (DPM_FLAGS_PARTNER_DR_POWER | DPM_FLAGS_PARTNER_DR_DATA |\ + DPM_FLAGS_PARTNER_EXTPOWER | DPM_FLAGS_PARTNER_USB_COMM |\ + DPM_FLAGS_PARTNER_USB_SUSPEND) + +/* DPM_CAPS */ + +#define DPM_CAP_LOCAL_DR_POWER (1<<0) +#define DPM_CAP_LOCAL_DR_DATA (1<<1) +#define DPM_CAP_LOCAL_EXT_POWER (1<<2) +#define DPM_CAP_LOCAL_USB_COMM (1<<3) +#define DPM_CAP_LOCAL_USB_SUSPEND (1<<4) +#define DPM_CAP_LOCAL_HIGH_CAP (1<<5) +#define DPM_CAP_LOCAL_GIVE_BACK (1<<6) +#define DPM_CAP_LOCAL_NO_SUSPEND (1<<7) +#define DPM_CAP_LOCAL_VCONN_SUPPLY (1<<8) + +#define DPM_CAP_ATTEMP_ENTER_DC_MODE (1<<11) +#define DPM_CAP_ATTEMP_DISCOVER_CABLE_DFP (1<<12) +#define DPM_CAP_ATTEMP_ENTER_DP_MODE (1<<13) +#define DPM_CAP_ATTEMP_DISCOVER_CABLE (1<<14) +#define DPM_CAP_ATTEMP_DISCOVER_ID (1<<15) +#define DPM_CAP_ATTEMP_DISCOVER_SVID (1<<16) + +enum dpm_cap_pr_check_prefer { + DPM_CAP_PR_CHECK_DISABLE = 0, + DPM_CAP_PR_CHECK_PREFER_SNK = 1, + DPM_CAP_PR_CHECK_PREFER_SRC = 2, +}; + +#define DPM_CAP_PR_CHECK_PROP(cap) ((cap & 0x03) << 18) +#define DPM_CAP_EXTRACT_PR_CHECK(raw) ((raw >> 18) & 0x03) +#define DPM_CAP_PR_SWAP_REJECT_AS_SRC (1<<20) +#define DPM_CAP_PR_SWAP_REJECT_AS_SNK (1<<21) +#define DPM_CAP_PR_SWAP_CHECK_GP_SRC (1<<22) +#define DPM_CAP_PR_SWAP_CHECK_GP_SNK (1<<23) +#define DPM_CAP_PR_SWAP_CHECK_GOOD_POWER \ + (DPM_CAP_PR_SWAP_CHECK_GP_SRC | DPM_CAP_PR_SWAP_CHECK_GP_SNK) + +#define DPM_CAP_CHECK_EXT_POWER \ + (DPM_CAP_LOCAL_EXT_POWER | DPM_CAP_PR_SWAP_CHECK_GOOD_POWER) + +enum dpm_cap_dr_check_prefer { + DPM_CAP_DR_CHECK_DISABLE = 0, + DPM_CAP_DR_CHECK_PREFER_UFP = 1, + DPM_CAP_DR_CHECK_PREFER_DFP = 2, +}; + +#define DPM_CAP_DR_CHECK_PROP(cap) ((cap & 0x03) << 22) +#define DPM_CAP_EXTRACT_DR_CHECK(raw) ((raw >> 22) & 0x03) +#define DPM_CAP_DR_SWAP_REJECT_AS_DFP (1<<24) +#define DPM_CAP_DR_SWAP_REJECT_AS_UFP (1<<25) + +#define DPM_CAP_DP_PREFER_MF (1<<29) + + +/* Power Data Object related structure */ + +struct tcpm_power_cap { + uint8_t cnt; + uint32_t pdos[PDO_MAX_NR]; +}; + +struct tcpm_remote_power_cap { + uint8_t selected_cap_idx; + uint8_t nr; + int max_mv[PDO_MAX_NR]; + int min_mv[PDO_MAX_NR]; + int ma[PDO_MAX_NR]; + uint8_t type[PDO_MAX_NR]; +}; + +enum tcpm_power_cap_val_type { + TCPM_POWER_CAP_VAL_TYPE_FIXED = 0, + TCPM_POWER_CAP_VAL_TYPE_BATTERY = 1, + TCPM_POWER_CAP_VAL_TYPE_VARIABLE = 2, + TCPM_POWER_CAP_VAL_TYPE_AUGMENT = 3, + + TCPM_POWER_CAP_VAL_TYPE_UNKNOWN = 0xff, +}; + +#define TCPM_APDO_TYPE_MASK (0x0f) + +enum tcpm_power_cap_apdo_type { + TCPM_POWER_CAP_APDO_TYPE_PPS = 1 << 0, + + TCPM_POWER_CAP_APDO_TYPE_PPS_CF = (1 << 7), +}; + +struct tcpm_power_cap_val { + uint8_t type; + uint8_t apdo_type; + uint8_t pwr_limit; + + int max_mv; + int min_mv; + + union { + int uw; + int ma; + }; +}; + +struct tcpm_power_cap_list { + uint8_t nr; + struct tcpm_power_cap_val cap_val[PDO_MAX_NR]; +}; + +/* Request TCPM to execure PD/VDM function */ + +struct tcp_dpm_event; + +enum tcp_dpm_return_code { + TCP_DPM_RET_SUCCESS = 0, + TCP_DPM_RET_SENT = 0, + TCP_DPM_RET_VDM_ACK = 0, + + TCP_DPM_RET_DENIED_UNKNOWN, + TCP_DPM_RET_DENIED_NOT_READY, + TCP_DPM_RET_DENIED_LOCAL_CAP, + TCP_DPM_RET_DENIED_PARTNER_CAP, + TCP_DPM_RET_DENIED_SAME_ROLE, + TCP_DPM_RET_DENIED_INVALID_REQUEST, + TCP_DPM_RET_DENIED_REPEAT_REQUEST, + TCP_DPM_RET_DENIED_WRONG_DATA_ROLE, + TCP_DPM_RET_DENIED_PD_REV, + + TCP_DPM_RET_DROP_CC_DETACH, + TCP_DPM_RET_DROP_SENT_SRESET, + TCP_DPM_RET_DROP_RECV_SRESET, + TCP_DPM_RET_DROP_SENT_HRESET, + TCP_DPM_RET_DROP_RECV_HRESET, + TCP_DPM_RET_DROP_ERROR_REOCVERY, + TCP_DPM_RET_DROP_SEND_BIST, + TCP_DPM_RET_DROP_PE_BUSY, /* SinkTXNg*/ + + TCP_DPM_RET_WAIT, + TCP_DPM_RET_REJECT, + TCP_DPM_RET_TIMEOUT, + TCP_DPM_RET_VDM_NAK, + TCP_DPM_RET_NOT_SUPPORT, + + TCP_DPM_RET_BK_TIMEOUT, + TCP_DPM_RET_NO_RESPONSE, + + TCP_DPM_RET_NR, +}; + +enum TCP_DPM_EVT_ID { + TCP_DPM_EVT_UNKONW = 0, + + TCP_DPM_EVT_PD_COMMAND, + + TCP_DPM_EVT_PR_SWAP_AS_SNK = TCP_DPM_EVT_PD_COMMAND, + TCP_DPM_EVT_PR_SWAP_AS_SRC, + TCP_DPM_EVT_DR_SWAP_AS_UFP, + TCP_DPM_EVT_DR_SWAP_AS_DFP, + TCP_DPM_EVT_VCONN_SWAP_OFF, + TCP_DPM_EVT_VCONN_SWAP_ON, + TCP_DPM_EVT_GOTOMIN, + + TCP_DPM_EVT_SOFTRESET, + TCP_DPM_EVT_CABLE_SOFTRESET, + + TCP_DPM_EVT_GET_SOURCE_CAP, + TCP_DPM_EVT_GET_SINK_CAP, + + TCP_DPM_EVT_REQUEST, + TCP_DPM_EVT_REQUEST_EX, + TCP_DPM_EVT_REQUEST_AGAIN, + TCP_DPM_EVT_BIST_CM2, + + TCP_DPM_EVT_DUMMY, /* wakeup event thread */ + +#ifdef CONFIG_USB_PD_REV30 + TCP_DPM_EVT_PD30_COMMAND, + TCP_DPM_EVT_GET_SOURCE_CAP_EXT = TCP_DPM_EVT_PD30_COMMAND, + TCP_DPM_EVT_GET_STATUS, + TCP_DPM_EVT_FR_SWAP_AS_SINK, + TCP_DPM_EVT_FR_SWAP_AS_SOURCE, + TCP_DPM_EVT_GET_COUNTRY_CODE, + TCP_DPM_EVT_GET_PPS_STATUS, + + TCP_DPM_EVT_ALERT, + TCP_DPM_EVT_GET_COUNTRY_INFO, + + TCP_DPM_EVT_GET_BAT_CAP, + TCP_DPM_EVT_GET_BAT_STATUS, + TCP_DPM_EVT_GET_MFRS_INFO, +#endif /* CONFIG_USB_PD_REV30 */ + + TCP_DPM_EVT_VDM_COMMAND, + TCP_DPM_EVT_DISCOVER_CABLE = TCP_DPM_EVT_VDM_COMMAND, + TCP_DPM_EVT_DISCOVER_ID, + TCP_DPM_EVT_DISCOVER_SVIDS, + TCP_DPM_EVT_DISCOVER_MODES, + TCP_DPM_EVT_ENTER_MODE, + TCP_DPM_EVT_EXIT_MODE, + TCP_DPM_EVT_ATTENTION, + +#ifdef CONFIG_USB_PD_ALT_MODE + TCP_DPM_EVT_DP_ATTENTION, +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + TCP_DPM_EVT_DP_STATUS_UPDATE, + TCP_DPM_EVT_DP_CONFIG, +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + TCP_DPM_EVT_UVDM, +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + + TCP_DPM_EVT_IMMEDIATELY, + TCP_DPM_EVT_HARD_RESET = TCP_DPM_EVT_IMMEDIATELY, + TCP_DPM_EVT_ERROR_RECOVERY, + + TCP_DPM_EVT_NR, +}; + +typedef int (*tcp_dpm_event_cb)( + struct tcpc_device *tcpc, int ret, struct tcp_dpm_event *event); + +struct tcp_dpm_event_cb_data { + void *user_data; + tcp_dpm_event_cb event_cb; +}; + +static const struct tcp_dpm_event_cb_data tcp_dpm_evt_cb_null = { + .user_data = NULL, + .event_cb = NULL, +}; + +struct tcp_dpm_new_role { + uint8_t new_role; +}; + +struct tcp_dpm_pd_request { + int mv; + int ma; +}; + +struct tcp_dpm_pd_request_ex { + uint8_t pos; + + union { + uint32_t max; + uint32_t max_uw; + uint32_t max_ma; + }; + + union { + uint32_t oper; + uint32_t oper_uw; + uint32_t oper_ma; + }; +}; + +struct tcp_dpm_svdm_data { + uint16_t svid; + uint8_t ops; +}; + +struct tcp_dpm_dp_data { + uint32_t val; + uint32_t mask; +}; + +struct tcp_dpm_custom_vdm_data { + bool wait_resp; + uint8_t cnt; + uint32_t vdos[PD_DATA_OBJ_SIZE]; +}; + +struct tcp_dpm_event { + uint8_t event_id; + void *user_data; + tcp_dpm_event_cb event_cb; + + union { + struct tcp_dpm_pd_request pd_req; + struct tcp_dpm_pd_request_ex pd_req_ex; + + + struct tcp_dpm_dp_data dp_data; + struct tcp_dpm_custom_vdm_data vdm_data; + + struct tcp_dpm_svdm_data svdm_data; + + + struct pd_get_battery_capabilities gbcdb; + struct pd_get_battery_status gbsdb; + struct pd_get_manufacturer_info gmidb; + + uint32_t index; + uint32_t data_object[PD_DATA_OBJ_SIZE]; + } tcp_dpm_data; +}; + +/* KEEP_SVID */ + +struct tcpm_svid_list { + uint8_t cnt; + uint16_t svids[VDO_MAX_SVID_NR]; +}; + +struct tcpm_mode_list { + uint8_t cnt; + uint32_t modes[VDO_MAX_NR]; +}; + +/* ALT_DP */ + +enum pd_dp_ufp_u_state { + DP_UFP_U_NONE = 0, + DP_UFP_U_STARTUP, + DP_UFP_U_WAIT, + DP_UFP_U_OPERATION, + DP_UFP_U_STATE_NR, + + DP_UFP_U_ERR = 0X10, +}; + +enum pd_dp_dfp_u_state { + DP_DFP_U_NONE = 0, + DP_DFP_U_DISCOVER_ID, + DP_DFP_U_DISCOVER_SVIDS, + DP_DFP_U_DISCOVER_MODES, + DP_DFP_U_ENTER_MODE, + DP_DFP_U_STATUS_UPDATE, + DP_DFP_U_WAIT_ATTENTION, + DP_DFP_U_CONFIGURE, + DP_DFP_U_OPERATION, + DP_DFP_U_STATE_NR, + + DP_DFP_U_ERR = 0X10, + + DP_DFP_U_ERR_DISCOVER_ID_TYPE, + DP_DFP_U_ERR_DISCOVER_ID_NAK_TIMEOUT, + + DP_DFP_U_ERR_DISCOVER_SVID_DP_SID, + DP_DFP_U_ERR_DISCOVER_SVID_NAK_TIMEOUT, + + DP_DFP_U_ERR_DISCOVER_MODE_DP_SID, + DP_DFP_U_ERR_DISCOVER_MODE_CAP, /* NO SUPPORT UFP-D */ + DP_DFP_U_ERR_DISCOVER_MODE_NAK_TIMEROUT, + + DP_DFP_U_ERR_ENTER_MODE_DP_SID, + DP_DFP_U_ERR_ENTER_MODE_NAK_TIMEOUT, + + DP_DFP_U_ERR_EXIT_MODE_DP_SID, + DP_DFP_U_ERR_EXIT_MODE_NAK_TIMEOUT, + + DP_DFP_U_ERR_STATUS_UPDATE_DP_SID, + DP_DFP_U_ERR_STATUS_UPDATE_NAK_TIMEOUT, + DP_DFP_U_ERR_STATUS_UPDATE_ROLE, + + DP_DFP_U_ERR_CONFIGURE_SELECT_MODE, +}; + +/* Custom VDM */ + +#define PD_UVDM_HDR(vid, custom) \ + (((vid) << 16) | ((custom) & 0x7FFF)) + +#define PD_SVDM_HDR(vid, custom) \ + (((vid) << 16) | (1<<15) | ((custom) & 0x1FDF)) + +#define PD_UVDM_HDR_CMD(hdr) \ + (hdr & 0x7FFF) + +#define PD_SVDM_HDR_CMD(hdr) \ + (hdr & 0x1FDF) + +#define DPM_CHARGING_POLICY_MASK (0x0f) + +/* Charging Policy */ + +enum dpm_charging_policy { + /* VSafe5V only */ + DPM_CHARGING_POLICY_VSAFE5V = 0, + + /* Max Power */ + DPM_CHARGING_POLICY_MAX_POWER = 1, + + /* Custom defined Policy */ + DPM_CHARGING_POLICY_CUSTOM = 2, + + /* Runtime Policy, restore to default after plug-out or hard-reset */ + DPM_CHARGING_POLICY_RUNTIME = 3, + + /* Direct charge */ + DPM_CHARGING_POLICY_DIRECT_CHARGE = 3, + + /* PPS */ + DPM_CHARGING_POLICY_PPS = 4, + + /* Default Charging Policy */ + DPM_CHARGING_POLICY_DEFAULT = 0xff, + + DPM_CHARGING_POLICY_IGNORE_MISMATCH_CURR = 1 << 4, + DPM_CHARGING_POLICY_PREFER_LOW_VOLTAGE = 1 << 5, + DPM_CHARGING_POLICY_PREFER_HIGH_VOLTAGE = 1 << 6, + + DPM_CHARGING_POLICY_MAX_POWER_LV = + DPM_CHARGING_POLICY_MAX_POWER | + DPM_CHARGING_POLICY_PREFER_LOW_VOLTAGE, + DPM_CHARGING_POLICY_MAX_POWER_LVIC = + DPM_CHARGING_POLICY_MAX_POWER_LV | + DPM_CHARGING_POLICY_IGNORE_MISMATCH_CURR, + + DPM_CHARGING_POLICY_MAX_POWER_HV = + DPM_CHARGING_POLICY_MAX_POWER | + DPM_CHARGING_POLICY_PREFER_HIGH_VOLTAGE, + DPM_CHARGING_POLICY_MAX_POWER_HVIC = + DPM_CHARGING_POLICY_MAX_POWER_HV | + DPM_CHARGING_POLICY_IGNORE_MISMATCH_CURR, + + /* DPM_CHARGING_POLICY_PPS */ + + DPM_CHARGING_POLICY_PPS_IC = + DPM_CHARGING_POLICY_PPS | + DPM_CHARGING_POLICY_IGNORE_MISMATCH_CURR, +}; + +#if IS_ENABLED(CONFIG_TCPC_CLASS) + +extern struct tcpc_device + *tcpc_dev_get_by_name(const char *name); + +extern int register_tcp_dev_notifier(struct tcpc_device *tcp_dev, + struct notifier_block *nb, uint8_t flags); +extern int unregister_tcp_dev_notifier(struct tcpc_device *tcp_dev, + struct notifier_block *nb, uint8_t flags); + +extern int tcpm_shutdown(struct tcpc_device *tcpc); + +extern int tcpm_inquire_remote_cc(struct tcpc_device *tcpc, + uint8_t *cc1, uint8_t *cc2, bool from_ic); +extern int tcpm_inquire_vbus_level(struct tcpc_device *tcpc, bool from_ic); +extern int tcpm_inquire_typec_remote_rp_curr(struct tcpc_device *tcpc); +extern bool tcpm_inquire_cc_polarity(struct tcpc_device *tcpc); +extern uint8_t tcpm_inquire_typec_attach_state(struct tcpc_device *tcpc); +extern uint8_t tcpm_inquire_typec_role(struct tcpc_device *tcpc); +extern uint8_t tcpm_inquire_typec_local_rp(struct tcpc_device *tcpc); + +extern int tcpm_typec_set_wake_lock( + struct tcpc_device *tcpc, bool user_lock); + +extern int tcpm_typec_set_usb_sink_curr( + struct tcpc_device *tcpc, int curr); + +extern int tcpm_typec_set_rp_level( + struct tcpc_device *tcpc, uint8_t level); + +extern int tcpm_typec_set_custom_hv( + struct tcpc_device *tcpc, bool en); + +extern int tcpm_typec_role_swap( + struct tcpc_device *tcpc); + +extern int tcpm_typec_change_role( + struct tcpc_device *tcpc, uint8_t typec_role); + +extern int tcpm_typec_change_role_postpone( + struct tcpc_device *tcpc, uint8_t typec_role, bool postpone); + +extern int tcpm_typec_error_recovery(struct tcpc_device *tcpc); + +extern int tcpm_typec_disable_function( + struct tcpc_device *tcpc, bool disable); + +#ifdef CONFIG_USB_POWER_DELIVERY + +extern bool tcpm_inquire_pd_connected( + struct tcpc_device *tcpc); + +extern bool tcpm_inquire_pd_prev_connected( + struct tcpc_device *tcpc); + +extern uint8_t tcpm_inquire_pd_data_role( + struct tcpc_device *tcpc); + +extern uint8_t tcpm_inquire_pd_power_role( + struct tcpc_device *tcpc); + +extern uint8_t tcpm_inquire_pd_vconn_role( + struct tcpc_device *tcpc); + +extern uint8_t tcpm_inquire_pd_pe_ready( + struct tcpc_device *tcpc); + +extern uint8_t tcpm_inquire_cable_current( + struct tcpc_device *tcpc); + +extern uint32_t tcpm_inquire_dpm_flags( + struct tcpc_device *tcpc); + +extern uint32_t tcpm_inquire_dpm_caps( + struct tcpc_device *tcpc); + +extern void tcpm_set_dpm_caps( + struct tcpc_device *tcpc, uint32_t caps); + +/* Request TCPM to send PD Request */ + +extern int tcpm_put_tcp_dpm_event( + struct tcpc_device *tcpc, struct tcp_dpm_event *event); + +/* TCPM DPM PD I/F */ + +extern int tcpm_inquire_pd_contract( + struct tcpc_device *tcpc, int *mv, int *ma); +extern int tcpm_inquire_cable_inform( + struct tcpc_device *tcpc, uint32_t *vdos); +extern int tcpm_inquire_pd_partner_inform( + struct tcpc_device *tcpc, uint32_t *vdos); +extern int tcpm_inquire_pd_partner_svids( + struct tcpc_device *tcpc, struct tcpm_svid_list *list); +extern int tcpm_inquire_pd_partner_modes( + struct tcpc_device *tcpc, uint16_t svid, struct tcpm_mode_list *list); +extern int tcpm_inquire_pd_source_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap *cap); +extern int tcpm_inquire_pd_sink_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap *cap); + +extern bool tcpm_extract_power_cap_val( + uint32_t pdo, struct tcpm_power_cap_val *cap); + +extern bool tcpm_extract_power_cap_list( + struct tcpm_power_cap *cap, struct tcpm_power_cap_list *cap_list); + +extern int tcpm_get_remote_power_cap(struct tcpc_device *tcpc, + struct tcpm_remote_power_cap *cap); + +extern int tcpm_inquire_select_source_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap_val *cap_val); + +/* Request TCPM to send PD Request */ + +extern int tcpm_dpm_pd_power_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_data_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_vconn_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_goto_min(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_soft_reset(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_get_source_cap(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_get_sink_cap(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_request(struct tcpc_device *tcpc, + int mv, int ma, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_request_ex(struct tcpc_device *tcpc, + uint8_t pos, uint32_t max, uint32_t oper, + const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_bist_cm2(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data); + +#ifdef CONFIG_USB_PD_REV30 +extern int tcpm_dpm_pd_get_source_cap_ext(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data, + struct pd_source_cap_ext *src_cap_ext); +extern int tcpm_dpm_pd_fast_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_get_status(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data, struct pd_status *status); +extern int tcpm_dpm_pd_get_pps_status(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data, + struct pd_pps_status *pps_status); +extern int tcpm_dpm_pd_get_country_code(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data, + struct pd_country_codes *ccdb); +extern int tcpm_dpm_pd_get_country_info(struct tcpc_device *tcpc, + uint32_t ccdo, const struct tcp_dpm_event_cb_data *data, + struct pd_country_info *cidb); +extern int tcpm_dpm_pd_get_bat_cap(struct tcpc_device *tcpc, + struct pd_get_battery_capabilities *gbcdb, + const struct tcp_dpm_event_cb_data *data, + struct pd_battery_capabilities *bcdb); +extern int tcpm_dpm_pd_get_bat_status(struct tcpc_device *tcpc, + struct pd_get_battery_status *gbsdb, + const struct tcp_dpm_event_cb_data *data, uint32_t *bsdo); +extern int tcpm_dpm_pd_get_mfrs_info(struct tcpc_device *tcpc, + struct pd_get_manufacturer_info *gmidb, + const struct tcp_dpm_event_cb_data *data, + struct pd_manufacturer_info *midb); +extern int tcpm_dpm_pd_alert(struct tcpc_device *tcpc, + uint32_t ado, const struct tcp_dpm_event_cb_data *data); +#endif /* CONFIG_USB_PD_REV30 */ + +extern int tcpm_dpm_pd_hard_reset(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_pd_error_recovery(struct tcpc_device *tcpc); + +/* Request TCPM to send SOP' request */ + +extern int tcpm_dpm_pd_cable_soft_reset( + struct tcpc_device *tcpc, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_vdm_discover_cable( + struct tcpc_device *tcpc, const struct tcp_dpm_event_cb_data *data); + +/* Request TCPM to send VDM request */ + +extern int tcpm_dpm_vdm_discover_id( + struct tcpc_device *tcpc, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_vdm_discover_svid( + struct tcpc_device *tcpc, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_vdm_discover_mode(struct tcpc_device *tcpc, + uint16_t svid, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_vdm_enter_mode(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_vdm_exit_mode(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, const struct tcp_dpm_event_cb_data *data); +extern int tcpm_dpm_vdm_attention(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, const struct tcp_dpm_event_cb_data *data); + +/* Request TCPM to send DP Request */ + +#ifdef CONFIG_USB_PD_ALT_MODE + +extern int tcpm_inquire_dp_ufp_u_state( + struct tcpc_device *tcpc, uint8_t *state); + +extern int tcpm_dpm_dp_attention(struct tcpc_device *tcpc, + uint32_t dp_status, uint32_t mask, + const struct tcp_dpm_event_cb_data *data); + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + +extern int tcpm_inquire_dp_dfp_u_state( + struct tcpc_device *tcpc, uint8_t *state); + +extern int tcpm_dpm_dp_status_update(struct tcpc_device *tcpc, + uint32_t dp_status, uint32_t mask, + const struct tcp_dpm_event_cb_data *data); + +extern int tcpm_dpm_dp_config(struct tcpc_device *tcpc, + uint32_t dp_config, uint32_t mask, + const struct tcp_dpm_event_cb_data *data); +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +/* Request TCPM to send PD-UVDM Request */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + +extern int tcpm_dpm_send_custom_vdm( + struct tcpc_device *tcpc, + struct tcp_dpm_custom_vdm_data *vdm_data, + const struct tcp_dpm_event_cb_data *cb_data); + +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +/* Notify TCPM */ + +extern int tcpm_notify_vbus_stable(struct tcpc_device *tcpc); + +/* Charging Policy: Select PDO */ + +extern int tcpm_reset_pd_charging_policy(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data); + +extern int tcpm_set_pd_charging_policy(struct tcpc_device *tcpc, + uint8_t policy, const struct tcp_dpm_event_cb_data *data); + +extern int tcpm_set_pd_charging_policy_default( + struct tcpc_device *tcpc, uint8_t policy); + +extern uint8_t tcpm_inquire_pd_charging_policy(struct tcpc_device *tcpc); +extern uint8_t tcpm_inquire_pd_charging_policy_default( + struct tcpc_device *tcpc); + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE +extern int tcpm_set_direct_charge_en(struct tcpc_device *tcpc, bool en); +extern bool tcpm_inquire_during_direct_charge(struct tcpc_device *tcpc); +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE +extern int tcpm_dpm_set_vconn_supply_mode( + struct tcpc_device *tcpc, uint8_t mode); +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +extern int tcpm_set_apdo_charging_policy( + struct tcpc_device *tcpc, uint8_t policy, int mv, int ma, + const struct tcp_dpm_event_cb_data *data); +extern int tcpm_inquire_pd_source_apdo(struct tcpc_device *tcpc, + uint8_t apdo_type, uint8_t *cap_i, struct tcpm_power_cap_val *cap); +extern bool tcpm_inquire_during_pps_charge(struct tcpc_device *tcpc); +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO + +/** + * tcpm_update_bat_status + * + * Update current capacity and charging status of the specified battery + * + * If the battery's real capacity in not known, + * all batteries's capacity can be updated with SoC. + * + * If the battery status is only update when a noticiation is received, + * using no_mutex version, otherwise it will cause deadlock. + * + * This function may trigger DPM to send alert message + * + * @ ref : Specifies which battery to update + * @ status : refer to BSDO_BAT_INFO, idle, charging, discharging + * @ wh : current capacity, unit 1/10 wh + * @ soc : current soc, unit: 0.1 % + * + */ + +extern int tcpm_update_bat_status_wh(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t wh); + +extern int tcpm_update_bat_status_wh_no_mutex(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t wh); + +extern int tcpm_update_bat_status_soc(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t soc); + +extern int tcpm_update_bat_status_soc_no_mutex(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t soc); + +/** + * tcpm_update_bat_last_full + * + * Update last full capacity of the specified battery + * + * If the battery status is only update when a noticiation is received, + * using no_mutex version, otherwise it will cause deadlock. + * + * @ ref : Specifies which battery to update + * @ wh : current capacity, unit 1/10 wh + * + */ + +extern int tcpm_update_bat_last_full(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint16_t wh); + +extern int tcpm_update_bat_last_full_no_mutex(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint16_t wh); + +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + +/** + * tcpm_update_pd_status + * + * Update local status: temperature, power input, and OT/OC/OV event. + * + * This function may trigger DPM to send alert message + * + * @ ptf : + * Present Temperature Flag + * @ temperature : + * 0 = feature not supported + * 1 = temperature is less than 2 degree c. + * 2-255 = temperature in degree c. + * + * @ input/input_mask : + * refer to pd_status.present_input + * + * @ bat_in / bat_in_mask : + * refer to pd_status.present_battey_input + * This function will auto_update INT_POWER_BAT + * + * @ evt : + * refer to pd_status.event_flags + */ + +extern int tcpm_update_pd_status_temp(struct tcpc_device *tcpc, + enum pd_present_temperature_flag ptf, uint8_t temperature); + +extern int tcpm_update_pd_status_input( + struct tcpc_device *tcpc, uint8_t input, uint8_t mask); + +extern int tcpm_update_pd_status_bat_input( + struct tcpc_device *tcpc, uint8_t bat_input, uint8_t bat_mask); + +extern int tcpm_update_pd_status_event( + struct tcpc_device *tcpc, uint8_t evt); + +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + + +#endif /* CONFIG_USB_PD_REV30 */ + +#endif /* CONFIG_USB_POWER_DELIVERY */ +#endif /* CONFIG_TCPC_CLASS */ + +/* Empty function if configuration not defined */ + +#define TCPC_CLASS_NA +#define USB_POWER_DELIVERY_NA +#define USB_PD_REV30_NA +#define USB_PD_ALT_MODE_NA +#define USB_PD_ALT_MODE_DFP_NA +#define USB_PD_CUSTOM_VDM_NA +#define USB_PD_DIRECT_CHARGE_NA +#define TCPC_VCONN_SUPPLY_MODE_NA +#define USB_PD_REV30_PPS_SINK_NA +#define USB_PD_REV30_BAT_INFO_NA +#define USB_PD_REV30_STATUS_NA + +#if IS_ENABLED(CONFIG_TCPC_CLASS) +#undef TCPC_CLASS_NA + +#ifdef CONFIG_USB_POWER_DELIVERY +#undef USB_POWER_DELIVERY_NA + +#ifdef CONFIG_USB_PD_REV30 +#undef USB_PD_REV30_NA +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +#undef USB_PD_REV30_PPS_SINK_NA +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +#ifdef CONFIG_USB_PD_REV30_BAT_INFO +#undef USB_PD_REV30_BAT_INFO_NA +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL +#undef USB_PD_REV30_STATUS_NA +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_ALT_MODE +#undef USB_PD_ALT_MODE_NA +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +#undef USB_PD_ALT_MODE_DFP_NA +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM +#undef USB_PD_CUSTOM_VDM_NA +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE +#undef USB_PD_DIRECT_CHARGE_NA +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE +#undef TCPC_VCONN_SUPPLY_MODE_NA +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + +#endif /* CONFIG_USB_POWER_DELIVERY */ +#endif /* CONFIG_TCPC_CLASS */ + +#ifdef TCPC_CLASS_NA + +static inline struct tcpc_device + *tcpc_dev_get_by_name(const char *name) +{ + return NULL; +} + +static inline int register_tcp_dev_notifier(struct tcpc_device *tcp_dev, + struct notifier_block *nb, uint8_t flags) +{ + return -ENODEV; +} + +static inline int unregister_tcp_dev_notifier(struct tcpc_device *tcp_dev, + struct notifier_block *nb, uint8_t flags) +{ + return -ENODEV; +} + +static inline int tcpm_shutdown(struct tcpc_device *tcpc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_remote_cc(struct tcpc_device *tcpc, + uint8_t *cc1, uint8_t *cc2, bool from_ic) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_vbus_level( + struct tcpc_device *tcpc, bool from_ic) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_typec_remote_rp_curr( + struct tcpc_device *tcpc) +{ + return 0; +} + +static inline bool tcpm_inquire_cc_polarity(struct tcpc_device *tcpc) +{ + return false; +} + +static inline uint8_t tcpm_inquire_typec_attach_state( + struct tcpc_device *tcpc) +{ + return TYPEC_UNATTACHED; +} + +static inline uint8_t tcpm_inquire_typec_role(struct tcpc_device *tcpc) +{ + return TYPEC_ROLE_UNKNOWN; +} + +static inline uint8_t tcpm_inquire_typec_local_rp(struct tcpc_device *tcpc) +{ + return 0; +} + +static inline int tcpm_typec_set_wake_lock( + struct tcpc_device *tcpc, bool user_lock) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_typec_set_usb_sink_curr( + struct tcpc_device *tcpc, int curr) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_typec_set_rp_level( + struct tcpc_device *tcpc, uint8_t level) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_typec_set_custom_hv( + struct tcpc_device *tcpc, bool en) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_typec_role_swap( + struct tcpc_device *tcpc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_typec_change_role( + struct tcpc_device *tcpc, uint8_t typec_role) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_typec_change_role_postpone( + struct tcpc_device *tcpc, uint8_t typec_role, bool postpone) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_typec_error_recovery(struct tcpc_device *tcpc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* TCPC_CLASS_NA */ + +#ifdef USB_POWER_DELIVERY_NA + +static inline bool tcpm_inquire_pd_connected( + struct tcpc_device *tcpc) +{ + return false; +} + +static inline bool tcpm_inquire_pd_prev_connected( + struct tcpc_device *tcpc) +{ + return false; +} + +static inline uint8_t tcpm_inquire_pd_data_role( + struct tcpc_device *tcpc) +{ + return 0; +} + +static inline uint8_t tcpm_inquire_pd_power_role( + struct tcpc_device *tcpc) +{ + return 0; +} + +static inline uint8_t tcpm_inquire_pd_vconn_role( + struct tcpc_device *tcpc) +{ + return 0; +} + +static inline uint8_t tcpm_inquire_pd_pe_ready( + struct tcpc_device *tcpc) +{ + return 0; +} + +static inline uint8_t tcpm_inquire_cable_current( + struct tcpc_device *tcpc) +{ + return PD_CABLE_CURR_UNKNOWN; +} + +static inline uint32_t tcpm_inquire_dpm_flags( + struct tcpc_device *tcpc) +{ + return 0; +} + +static inline uint32_t tcpm_inquire_dpm_caps( + struct tcpc_device *tcpc) +{ + return 0; +} + +static inline void tcpm_set_dpm_caps( + struct tcpc_device *tcpc, uint32_t caps) +{ +} + +static inline int tcpm_put_tcp_dpm_event( + struct tcpc_device *tcpc, struct tcp_dpm_event *event) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_pd_contract( + struct tcpc_device *tcpc, int *mv, int *ma) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_cable_inform( + struct tcpc_device *tcpc, uint32_t *vdos) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_pd_partner_inform( + struct tcpc_device *tcpc, uint32_t *vdos) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_pd_partner_svids( + struct tcpc_device *tcpc, struct tcpm_svid_list *list) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_pd_partner_modes( + struct tcpc_device *tcpc, uint16_t svid, struct tcpm_mode_list *list) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_pd_source_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap *cap) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_pd_sink_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap *cap) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline bool tcpm_extract_power_cap_val( + uint32_t pdo, struct tcpm_power_cap_val *cap) +{ + return false; +} + +static inline bool tcpm_extract_power_cap_list( + struct tcpm_power_cap *cap, struct tcpm_power_cap_list *cap_list) +{ + return false; +} + +static inline int tcpm_get_remote_power_cap(struct tcpc_device *tcpc, + struct tcpm_remote_power_cap *cap) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_select_source_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap_val *cap_val) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_power_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_data_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_vconn_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_goto_min(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_soft_reset(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_source_cap(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_sink_cap(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_request(struct tcpc_device *tcpc, + int mv, int ma, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_request_ex(struct tcpc_device *tcpc, + uint8_t pos, uint32_t max, uint32_t oper, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_bist_cm2(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* USB_POWER_DELIVERY_NA */ + +#ifdef USB_PD_REV30_NA +static inline int tcpm_dpm_pd_get_source_cap_ext(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data, + struct pd_source_cap_ext *src_cap_ext) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_fast_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_status(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data, struct pd_status *status) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_pps_status(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data, + struct pd_pps_status *pps_status) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_country_code(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data, + struct pd_country_codes *ccdb) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_country_info(struct tcpc_device *tcpc, + uint32_t ccdo, const struct tcp_dpm_event_cb_data *data, + struct pd_country_info *cidb) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_bat_cap(struct tcpc_device *tcpc, + struct pd_get_battery_capabilities *gbcdb, + const struct tcp_dpm_event_cb_data *data, + struct pd_battery_capabilities *bcdb) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_bat_status(struct tcpc_device *tcpc, + struct pd_get_battery_status *gbsdb, + const struct tcp_dpm_event_cb_data *data, uint32_t *bsdo) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_get_mfrs_info(struct tcpc_device *tcpc, + struct pd_get_manufacturer_info *gmidb, + const struct tcp_dpm_event_cb_data *data, + struct pd_manufacturer_info *midb) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_alert(struct tcpc_device *tcpc, + uint32_t ado, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* USB_PD_REV30_NA */ + +#ifdef USB_POWER_DELIVERY_NA + +static inline int tcpm_dpm_pd_hard_reset(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_pd_error_recovery(struct tcpc_device *tcpc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +/* Request TCPM to send SOP' request */ + +static inline int tcpm_dpm_pd_cable_soft_reset( + struct tcpc_device *tcpc, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_vdm_discover_cable( + struct tcpc_device *tcpc, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +/* Request TCPM to send VDM request */ + +static inline int tcpm_dpm_vdm_discover_id( + struct tcpc_device *tcpc, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_vdm_discover_svid( + struct tcpc_device *tcpc, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_vdm_discover_mode(struct tcpc_device *tcpc, + uint16_t svid, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_vdm_enter_mode(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_vdm_attention(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_vdm_exit_mode(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* USB_POWER_DELIVERY_NA */ + +#ifdef USB_PD_ALT_MODE_NA +static inline int tcpm_inquire_dp_ufp_u_state( + struct tcpc_device *tcpc, uint8_t *state) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_dp_attention(struct tcpc_device *tcpc, + uint32_t dp_status, uint32_t mask, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* USB_PD_ALT_MODE_NA */ + +#ifdef USB_PD_ALT_MODE_DFP_NA +static inline int tcpm_inquire_dp_dfp_u_state( + struct tcpc_device *tcpc, uint8_t *state) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_dp_status_update(struct tcpc_device *tcpc, + uint32_t dp_status, uint32_t mask, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_dpm_dp_config(struct tcpc_device *tcpc, + uint32_t dp_config, uint32_t mask, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* USB_PD_ALT_MODE_DFP_NA */ + +#ifdef USB_PD_CUSTOM_VDM_NA +static inline int tcpm_dpm_send_custom_vdm( + struct tcpc_device *tcpc, + struct tcp_dpm_custom_vdm_data *vdm_data, + const struct tcp_dpm_event_cb_data *cb_data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* USB_PD_CUSTOM_VDM_NA */ + +#ifdef USB_POWER_DELIVERY_NA +static inline int tcpm_notify_vbus_stable(struct tcpc_device *tcpc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_reset_pd_charging_policy(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_set_pd_charging_policy(struct tcpc_device *tcpc, + uint8_t policy, const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_set_pd_charging_policy_default( + struct tcpc_device *tcpc, uint8_t policy) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline uint8_t tcpm_inquire_pd_charging_policy( + struct tcpc_device *tcpc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline uint8_t tcpm_inquire_pd_charging_policy_default( + struct tcpc_device *tcpc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* USB_POWER_DELIVERY_NA */ + +#ifdef USB_PD_DIRECT_CHARGE_NA +static inline int tcpm_set_direct_charge_en( + struct tcpc_device *tcpc, bool en) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline bool tcpm_inquire_during_direct_charge( + struct tcpc_device *tcpc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* USB_PD_DIRECT_CHARGE_NA */ + +#ifdef TCPC_VCONN_SUPPLY_MODE_NA +static inline int tcpm_dpm_set_vconn_supply_mode( + struct tcpc_device *tcpc, uint8_t mode) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} +#endif /* TCPC_VCONN_SUPPLY_MODE_NA */ + +#ifdef USB_PD_REV30_PPS_SINK_NA +static inline int tcpm_set_apdo_charging_policy( + struct tcpc_device *tcpc, uint8_t policy, int mv, int ma, + const struct tcp_dpm_event_cb_data *data) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_inquire_pd_source_apdo(struct tcpc_device *tcpc, + uint8_t apdo_type, uint8_t *cap_i, struct tcpm_power_cap_val *cap) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline bool tcpm_inquire_during_pps_charge(struct tcpc_device *tcpc) +{ + return false; +} + +#endif /* USB_PD_REV30_PPS_SINK_NA */ + +#ifdef USB_PD_REV30_BAT_INFO_NA + +static inline int tcpm_update_bat_status_wh(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t wh) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_update_bat_status_wh_no_mutex(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t wh) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_update_bat_status_soc(struct tcpc_device *tcpc, + uint8_t status, uint16_t soc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_update_bat_status_soc_no_mutex(struct tcpc_device *tcpc, + uint8_t status, uint16_t soc) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_update_bat_last_full(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint16_t wh) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_update_bat_last_full_no_mutex(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint16_t wh) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +#endif /* USB_PD_REV30_BAT_INFO_NA */ + + +#ifdef USB_PD_REV30_STATUS_NA + +static inline int tcpm_update_pd_status_temp(struct tcpc_device *tcpc, + enum pd_present_temperature_flag ptf, uint8_t temperature) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_update_pd_status_input( + struct tcpc_device *tcpc, uint8_t input, uint8_t mask) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_update_pd_status_bat_input( + struct tcpc_device *tcpc, uint8_t bat_input, uint8_t bat_mask) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +static inline int tcpm_update_pd_status_event( + struct tcpc_device *tcpc, uint8_t evt) +{ + return TCPM_ERROR_NO_IMPLEMENT; +} + +#endif /* USB_PD_REV30_STATUS_NA */ + +#undef TCPC_CLASS_NA +#undef USB_POWER_DELIVERY_NA +#undef USB_PD_REV30_NA +#undef USB_PD_ALT_MODE_NA +#undef USB_PD_ALT_MODE_DFP_NA +#undef USB_PD_CUSTOM_VDM_NA +#undef USB_PD_DIRECT_CHARGE_NA +#undef TCPC_VCONN_SUPPLY_MODE_NA +#undef USB_PD_REV30_PPS_SINK_NA +#undef USB_PD_REV30_BAT_INFO_NA +#undef USB_PD_REV30_STATUS_NA + +#endif /* TCPM_H_ */ diff --git a/drivers/usb/typec/mmi_tcpc/inc/tcpm_pd.h b/drivers/usb/typec/mmi_tcpc/inc/tcpm_pd.h new file mode 100644 index 000000000000..cd25b9cd4fdd --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/inc/tcpm_pd.h @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef TCPM_PD_H_ +#define TCPM_PD_H_ + +#include "tcpci_config.h" + +/* --- PD data message helpers --- */ + +#define PD_DATA_OBJ_SIZE (7) +#define PDO_MAX_NR (PD_DATA_OBJ_SIZE) +#define VDO_MAX_NR (PD_DATA_OBJ_SIZE-1) +#define VDO_MAX_SVID_NR (VDO_MAX_NR*2) + +#define VDO_DISCOVER_ID_IDH 0 +#define VDO_DISCOVER_ID_CSTAT 1 +#define VDO_DISCOVER_ID_PRODUCT 2 +#define VDO_DISCOVER_ID_CABLE 3 +#define VDO_DISCOVER_ID_AMA 3 + +/******************* PD30 *******************/ + +#ifdef CONFIG_USB_PD_REV30_CHUNKING_BY_PE +#define MAX_EXTENDED_MSG_LEN 260 +#else +#define MAX_EXTENDED_MSG_LEN 26 +#endif /* CONFIG_USB_PD_REV30_CHUNKING */ + +/* PD30 Data Message Data Object */ + +#define PD_BSDO_SIZE 1 +#define PD_CCDO_SIZE 1 +#define PD_ADO_SIZE 1 + +/* + * Battery Status Data Object (BSDO) + * ---------- + * <31:16> :: Battery Present Capacity (1/10 WH, 0xFFFF = SOC unknown) + * <15:8> :: Battery Info + * <7:0> :: Reserved and Shall be set to zero + */ + +#define BSDO(cap, info) (((cap) << 16) | ((info) << 8)) + +#define BSDO_BAT_INFO(x) ((x>>8) & 0xff) + +#define BSDO_BAT_INFO_INVALID_REF (1<<0) +#define BSDO_BAT_INFO_PRESENT (1<<1) +#define BSDO_BAT_INFO_CHARGING ((0<<2) | BSDO_BAT_INFO_PRESENT) +#define BSDO_BAT_INFO_DISCHARGING ((1<<2) | BSDO_BAT_INFO_PRESENT) +#define BSDO_BAT_INFO_IDLE ((2<<2) | BSDO_BAT_INFO_PRESENT) + +#define BSDO_BAT_CAP_UNKNOWN (0xffff) + +/* + * Country Code Data Object (CCDO) + * ---------- + * <31:24> :: First character of the Alpha-2 Country Code + * <23:16> :: Second character of the Alpha-2 Country Code + * <15:0> :: Reserved and Shall be set to zero + */ + +#define CCDO(code) ((code) << 16) + +#define CCDO_COUNTRY_CODE(x) ((x >> 16) & 0xffff) +#define CCDO_COUNTRY_CODE1(x) ((x >> 24) & 0xff) +#define CCDO_COUNTRY_CODE2(x) ((x >> 16) & 0xff) + +/* + * Alert Data Object (ADO) + * ---------- + * <31:24> :: Type Of Alert + * <23:20> :: Fixed Batteries (bit field) + * <19:16> :: Hot Swappable Batteries (bit field) + */ + +#define ADO_ALERT_BAT_CHANGED (1<<1) +#define ADO_ALERT_OCP (1<<2) +#define ADO_ALERT_OTP (1<<3) +#define ADO_ALERT_OPER_CHANGED (1<<4) +#define ADO_ALERT_SRC_IN_CHANGED (1<<5) +#define ADO_ALERT_OVP (1<<6) + +#define ADO_ALERT_TYPE(raw) (raw >> 24) +#define ADO_FIXED_BAT(raw) ((raw >> 20) & 0x0f) +#define ADO_HOT_SWAP_BAT(raw) ((raw >> 16) & 0x0f) + +#define ADO_ALERT_TYPE_SET(type) (type << 24) +#define ADO_FIXED_BAT_SET(i) ((i) << 20) +#define ADO_HOT_SWAP_BAT_SET(i) ((i) << 16) + +#define ADO(type, fixed, swap) \ + (((type) << 24) | ((fixed) << 20) | ((swap) << 16)) + +#define ADO_GET_STATUS_ONCE_MASK ADO(\ + ADO_ALERT_BAT_CHANGED|ADO_ALERT_SRC_IN_CHANGED,\ + 0xff, 0xff) + +/* PD30 Extend Message Data Object */ + +enum pd_present_temperature_flag { + PD_PTF_NO_SUPPORT = 0, + PD_PTF_NORMAL, + PD_PTF_WARNING, + PD_PTF_OVER_TEMP, +}; + +enum pd_battery_reference { + PD_BAT_REF_FIXED0 = 0, + PD_BAT_REF_FIXED1, + PD_BAT_REF_FIXED2, + PD_BAT_REF_FIXED3, + + PD_BAT_REF_SWAP0 = 4, + PD_BAT_REF_SWAP1, + PD_BAT_REF_SWAP2, + PD_BAT_REF_SWAP3, + + PD_BAT_REF_MAX, + + /* 8 ~ 255 are reserved and shall not be used */ +}; + + +/* SCEDB, Source_Capabilities_Extended */ + +#define PD_SCEDB_SIZE 24 + +#define PD_SCEDB_VR(load_step, ioc) \ + ((load_step) | (ioc << 2)) + +enum { + PD_SCEDB_VR_LOAD_STEP_150 = 0x00, + PD_SCEDB_VR_LOAD_STEP_500 = 0x01, +}; + +enum { + PD_SCEDB_VR_IOC_25 = 0x00, + PD_SCEDB_VR_IOC_90 = 0x01, +}; + +#define PD_SCEDB_COMPLIANCE_LPS (1<<0) +#define PD_SCEDB_COMPLIANCE_PS1 (1<<1) +#define PD_SCEDB_COMPLIANCE_PS2 (1<<2) + +#define PD_SCEDB_TC_LOW_TC (1<<0) +#define PD_SCEDB_TC_GROUND (1<<1) +#define PD_SCEDB_TC_GROUND_INTEND (1<<2) + +#define PD_SCEDB_TT_IEC_60950 0 +#define PD_SCEDB_TT_IEC_62368_TS1 1 +#define PD_SCEDB_TT_IEC_62368_TS2 2 + +#define PD_SCEDB_INPUT_EXT (1<<0) +#define PD_SCEDB_INPUT_EXT_UNCONSTRAINED (1<<1) +#define PD_SCEDB_INPUT_INT (1<<2) + +#define PD_SCEDB_BATTERIES(swap_nr, fixed_nr) \ + (swap_nr << 4 | fixed_nr) + +#define PD_SCEDB_FIX_BAT_NR(raw) (raw & 0xf) +#define PD_SCEDB_SWAP_BAT_NR(raw) ((raw >> 4) & 0xf) + +struct pd_source_cap_ext { + uint16_t vid; + uint16_t pid; + uint32_t xid; + uint8_t fw_ver; + uint8_t hw_ver; + uint8_t voltage_regulation; + uint8_t hold_time_ms; + uint8_t compliance; /* bit field */ + uint8_t touch_current; /* bit field */ + uint16_t peak_current[3]; + uint8_t touch_temp; + uint8_t source_inputs; /* bit field */ + uint8_t batteries; + uint8_t source_pdp; +}; + +/* GBSDB, Get_Battery_Status */ + +#define PD_GBSDB_SIZE 1 + +struct pd_get_battery_status { + uint8_t bat_status_ref; /* pd_battery_reference */ +}; + +/* GBCDB, Get_Battery_Cap */ + +#define PD_GBCDB_SIZE 1 + +struct pd_get_battery_capabilities { + uint8_t bat_cap_ref; /* pd_battery_reference */ +}; + +/* BCDB, Battery_Capabilities */ + +#define PD_BCDB_SIZE 9 + +#define PD_BCDB_BAT_CAP_NOT_PRESENT 0x0000 +#define PD_BCDB_BAT_CAP_UNKNOWN 0Xffff +#define PD_BCDB_BAT_CAP_RAW(cap_wh) (cap_wh*10) +#define PD_BCDB_BAT_CAP_VAL(raw) (raw/10) + +#define PD_BCDB_BAT_TYPE_INVALID (1<<0) + +struct pd_battery_capabilities { + uint16_t vid; + uint16_t pid; + uint16_t bat_design_cap; + uint16_t bat_last_full_cap; + uint8_t bat_type; +}; + +/* GMIDB, Get_Manufacturer_Info */ + +#define PD_GMIDB_SIZE 2 + +#define PD_GMIDB_TARGET_PORT 0 +#define PD_GMIDB_TARGET_BATTRY 1 + +struct pd_get_manufacturer_info { + uint8_t info_target; + uint8_t info_ref; +}; + +/* MIDB, Manufacturer_Info */ + +#define PD_MIDB_MIN_SIZE 4 +#define PD_MIDB_MAX_SIZE 26 +#define PD_MIDB_DYNAMIC_SIZE (PD_MIDB_MAX_SIZE-PD_MIDB_MIN_SIZE) + +struct pd_manufacturer_info { + uint16_t vid; + uint16_t pid; + uint8_t mfrs_string[PD_MIDB_DYNAMIC_SIZE]; +}; + +/* CCDB, Country_Codes */ + +#define PD_CCDB_MIN_SIZE 4 +#define PD_CCDB_MAX_SIZE MAX_EXTENDED_MSG_LEN +#define PD_CCDB_DYNAMIC_SIZE (PD_CCDB_MAX_SIZE-PD_CCDB_MIN_SIZE) + +struct pd_country_codes { + uint16_t length; + uint16_t country_code[1+PD_CCDB_DYNAMIC_SIZE/2]; +}; + +/* CIDB, country_info */ + +#define PD_CIDB_MIN_SIZE 4 +#define PD_CIDB_MAX_SIZE MAX_EXTENDED_MSG_LEN + +#define PD_CIDB_DYNAMIC_SIZE (PD_CIDB_MAX_SIZE-PD_CIDB_MIN_SIZE) + +struct pd_country_info { + uint16_t country_code; + uint16_t reserved; + uint8_t country_special_data[PD_CIDB_DYNAMIC_SIZE]; +}; + +/* SDB, Status */ + +#define PD_SDB_SIZE 6 + +#define PD_STATUS_INPUT_EXT_POWER (1<<1) +#define PD_STATUS_INPUT_EXT_POWER_FROM_AC (1<<2) +#define PD_STATUS_INPUT_INT_POWER_BAT (1<<3) +#define PD_STATUS_INPUT_INT_POWER_NOT_BAT (1<<4) + +#define PD_STASUS_EVENT_OCP (1<<1) +#define PD_STATUS_EVENT_OTP (1<<2) +#define PD_STATUS_EVENT_OVP (1<<3) +#define PD_STATUS_EVENT_CF_MODE (1<<4) + +#define PD_STASUS_EVENT_READ_CLEAR (\ + PD_STASUS_EVENT_OCP|PD_STATUS_EVENT_OTP|PD_STATUS_EVENT_OVP) + +#define PD_STASUS_EVENT_MASK (\ + PD_STASUS_EVENT_OCP|PD_STATUS_EVENT_OTP|PD_STATUS_EVENT_OVP|\ + PD_STATUS_EVENT_CF_MODE) + +#define PD_STATUS_TEMP_PTF(raw) ((raw & 0x06) >> 1) +#define PD_STATUS_TEMP_SET_PTF(val) ((val & 0x03) << 1) + +struct pd_status { + uint8_t internal_temp; /* 0 means no support */ + uint8_t present_input; /* bit filed */ + uint8_t present_battey_input; /* bit filed */ + uint8_t event_flags; /* bit filed */ + uint8_t temp_status; /* bit filed */ + uint8_t power_status; /* bit filed */ +}; + +/* PPSSDB, PPSStatus */ + +#define PD_PPSSDB_SIZE 4 /* PPS_Status */ + +#define PD_PPS_GET_OUTPUT_MV(raw) (raw*20) +#define PD_PPS_GET_OUTPUT_MA(raw) (raw*50) + +#define PD_PPS_SET_OUTPUT_MV(mv) (((mv) / 20) & 0xFFFF) +#define PD_PPS_SET_OUTPUT_MA(ma) (((ma) / 50) & 0xFF) + +#define PD_PPS_FLAGS_CFF (1 << 3) +#define PD_PPS_FLGAS_PTF(raw) ((raw & 0x06) >> 1) + +#define PD_PPS_FLAGS_SET_PTF(flags) ((flags & 0x03) << 1) + +struct pd_pps_status_raw { + uint16_t output_vol_raw; /* 0xffff means no support */ + uint8_t output_curr_raw; /* 0xff means no support */ + uint8_t real_time_flags; +}; + +struct pd_pps_status { + int output_mv; + int output_ma; + uint8_t real_time_flags; +}; +#endif /* TCPM_PD_H_ */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_core.c b/drivers/usb/typec/mmi_tcpc/pd_core.c new file mode 100644 index 000000000000..a28a4c733912 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_core.c @@ -0,0 +1,1477 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Core Driver + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "inc/tcpci.h" +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci_typec.h" +#include "inc/tcpci_event.h" +#include "inc/pd_policy_engine.h" +#ifdef CONFIG_DUAL_ROLE_USB_INTF +#include +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + +/* From DTS */ + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO +static inline void pd_parse_pdata_bat_info( + struct pd_port *pd_port, struct device_node *sub, + struct pd_battery_info *bat_info) +{ + int ret = 0; + u32 design_cap; + uint32_t vid, pid; + const char *mstring; + + struct pd_battery_capabilities *bat_cap = &bat_info->bat_cap; + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + struct pd_manufacturer_info *mfrs_info = &bat_info->mfrs_info; +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + + ret = of_property_read_u32(sub, "bat,vid", (u32 *)&vid); + if (ret < 0) { + pr_err("%s get pd vid fail\n", __func__); + vid = PD_IDH_VID(pd_port->id_vdos[0]); + } + + ret = of_property_read_u32(sub, "bat,pid", (u32 *)&pid); + if (ret < 0) { + pr_err("%s get pd pid fail\n", __func__); + pid = PD_PRODUCT_PID(pd_port->id_vdos[2]); + } + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + mfrs_info->vid = vid; + mfrs_info->pid = pid; + + ret = of_property_read_string(sub, "bat,mfrs", &mstring); + if (ret < 0) { + pr_err("%s get bat,mfrs fail\n", __func__); + mstring = "no_bat_mfrs_string"; + } + ret = snprintf(mfrs_info->mfrs_string, + sizeof(mfrs_info->mfrs_string), "%s", mstring); + if (ret < 0 || ret >= sizeof(mfrs_info->mfrs_string)) + pr_info("%s-%d snprintf fail\n", __func__, __LINE__); +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + + ret = of_property_read_u32(sub, "bat,design_cap", &design_cap); + if (ret < 0) { + bat_cap->bat_design_cap = PD_BCDB_BAT_CAP_UNKNOWN; + pr_err("%s get bat,dsn_cat fail\n", __func__); + } else { + bat_cap->bat_design_cap = (uint16_t) + PD_BCDB_BAT_CAP_RAW(design_cap); + } + + bat_cap->vid = vid; + bat_cap->pid = pid; + bat_cap->bat_last_full_cap = PD_BCDB_BAT_CAP_UNKNOWN; + + bat_info->bat_status = BSDO( + BSDO_BAT_CAP_UNKNOWN, BSDO_BAT_INFO_IDLE); +} + +static inline int pd_parse_pdata_bats( + struct pd_port *pd_port, struct device_node *np) +{ + u32 val; + int ret = 0, i; + struct device_node *sub; + char temp_string[26]; + + ret = of_property_read_u32(np, "bat,nr", &val); + if (ret < 0) { + pr_err("%s get pd bat NR fail\n", __func__); + pd_port->bat_nr = 0; + return 0; + } + + pd_port->bat_nr = val; + pr_info("%s Battery NR = %d\n", __func__, pd_port->bat_nr); + + pd_port->fix_bat_info = devm_kzalloc(&pd_port->tcpc->dev, + sizeof(struct pd_battery_info)*pd_port->bat_nr, + GFP_KERNEL); + + if (!pd_port->fix_bat_info) { + pr_err("%s get fix_bat_info memory fail\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < pd_port->bat_nr; i++) { + snprintf(temp_string, 26, "bat-info%d", i); + sub = of_find_node_by_name(np, temp_string); + if (!sub) { + pr_err("%s get sub bat node fail\n", __func__); + return -ENODEV; + } + + pd_parse_pdata_bat_info( + pd_port, sub, &pd_port->fix_bat_info[i]); + } + + for (i = 0; i < pd_port->bat_nr; i++) { + pr_info("%s fix_bat_info[%d].mfrs_info.vid = 0x%x, .mfrs_info.pid = 0x%x, .mfrs_string = %s, .bat_design_cap = %d\n", + __func__, i, + pd_port->fix_bat_info[i].mfrs_info.vid, + pd_port->fix_bat_info[i].mfrs_info.pid, + pd_port->fix_bat_info[i].mfrs_info.mfrs_string, + PD_BCDB_BAT_CAP_VAL( + pd_port->fix_bat_info[i].bat_cap.bat_design_cap)); + } + + return 0; +} +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY +static inline int pd_parse_pdata_country( + struct pd_port *pd_port, struct device_node *sub, + struct pd_country_authority *country_info) +{ + u32 val; + int ret = 0, j; + u32 *temp_u32; + + ret = of_property_read_u32(sub, "pd,country_code", &val); + if (ret < 0) { + pr_err("%s get country code fail\n", __func__); + return -ENODEV; + } + + country_info->code = (uint16_t) val; + + ret = of_property_read_u32(sub, "pd,country_len", &val); + if (ret < 0) { + pr_err("%s get country len fail\n", __func__); + return -ENODEV; + } + + country_info->len = (uint16_t) val; + + country_info->data = devm_kzalloc( + &pd_port->tcpc->dev, + sizeof(uint8_t)*country_info->len, + GFP_KERNEL); + + if (!country_info->data) { + pr_err("%s get country info data mem fail\n", + __func__); + return -ENOMEM; + } + + temp_u32 = devm_kzalloc(&pd_port->tcpc->dev, + sizeof(u32)*country_info->len, GFP_KERNEL); + + ret = of_property_read_u32_array(sub, "pd,country_data", + temp_u32, + country_info->len); + if (ret < 0) + pr_err("%s get country data fail\n", __func__); + + for (j = 0; j < country_info->len; j++) + country_info->data[j] = (uint8_t) temp_u32[j]; + + devm_kfree(&pd_port->tcpc->dev, temp_u32); + + return 0; +} + +static inline int pd_parse_pdata_countries( + struct pd_port *pd_port, struct device_node *np) +{ + int ret = 0, i, j; + struct device_node *sub; + char temp_string[26]; + + ret = of_property_read_u32(np, "pd,country_nr", + (u32 *)&pd_port->country_nr); + if (ret < 0) { + pr_err("%s get country nr fail\n", __func__); + pd_port->country_nr = 0; + return 0; + } + + pr_info("%s Country NR = %d\n", __func__, pd_port->country_nr); + + pd_port->country_info = devm_kzalloc(&pd_port->tcpc->dev, + sizeof(struct pd_country_authority)*pd_port->country_nr, + GFP_KERNEL); + + if (!pd_port->country_info) { + pr_err("%s get country info memory fail\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < pd_port->country_nr; i++) { + snprintf(temp_string, 26, "country%d", i); + sub = of_find_node_by_name(np, temp_string); + if (!sub) { + pr_err("%s get sub country node fail\n", + __func__); + return -ENODEV; + } + + ret = pd_parse_pdata_country(pd_port, sub, + &pd_port->country_info[i]); + if (ret < 0) + return ret; + } + + for (i = 0; i < pd_port->country_nr; i++) { + pr_info("%s country_info[%d].code = 0x%x, .len = %d\n", + __func__, i, + pd_port->country_info[i].code, + pd_port->country_info[i].len); + for (j = 0; j < pd_port->country_info[i].len; j++) { + pr_info("%s country_info[%d].data[%d] = 0x%x\n", + __func__, i, j, + pd_port->country_info[i].data[j]); + } + } + return 0; +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY */ + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL +static void pd_parse_log_src_cap_ext(struct pd_source_cap_ext *cap) +{ + pr_info("%s vid = 0x%x, pid = 0x%x, xid = 0x%x, fw_ver = 0x%x, hw_ver = 0x%0x\n", + __func__, + cap->vid, cap->pid, cap->xid, + cap->fw_ver, cap->hw_ver); + + pr_info("%s voltage_regulation = %d, hold_time_ms = %d, compliance = 0x%x, touch_current = 0x%x, peak_current = %d %d %d\n", + __func__, + cap->voltage_regulation, + cap->hold_time_ms, + cap->compliance, + cap->touch_current, + cap->peak_current[0], + cap->peak_current[1], + cap->peak_current[2]); + + pr_info("%s touch_temp = %d, source_inputs = 0x%x, batteries = 0x%x, source_pdp = 0x%x\n", + __func__, + cap->touch_temp, + cap->source_inputs, + cap->batteries, + cap->source_pdp); +} +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + +static inline void pd_parse_pdata_src_cap_ext( + struct pd_port *pd_port, struct device_node *np) +{ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + int ret = 0; + + ret = of_property_read_u32_array(np, "pd,source-cap-ext", + (u32 *) &pd_port->src_cap_ext, + sizeof(struct pd_source_cap_ext)/4); + + if (ret < 0) + pr_err("%s get source-cap-ext fail\n", __func__); + else + pd_parse_log_src_cap_ext(&pd_port->src_cap_ext); + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO + pd_port->src_cap_ext.batteries = + PD_SCEDB_BATTERIES(0, pd_port->bat_nr); + + if (pd_port->src_cap_ext.batteries) + pd_port->src_cap_ext.source_inputs |= PD_SCEDB_INPUT_INT; +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +} + +static inline void pd_parse_pdata_mfrs( + struct pd_port *pd_port, struct device_node *np) +{ + int ret = 0; + uint32_t vid, pid; +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + const char *mstring; +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + struct pd_manufacturer_info *mfrs_info = &pd_port->mfrs_info; +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + + ret = of_property_read_u32(np, "pd,vid", (u32 *)&vid); + if (ret < 0) { + pr_err("%s get pd vid fail\n", __func__); + vid = PD_IDH_VID(pd_port->id_vdos[0]); + } + + ret = of_property_read_u32(np, "pd,pid", (u32 *)&pid); + if (ret < 0) { + pr_err("%s get pd pid fail\n", __func__); + pid = PD_PRODUCT_PID(pd_port->id_vdos[2]); + } + + pr_info("%s VID = 0x%x, PID = 0x%x\n", __func__, vid, pid); + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + mfrs_info->vid = vid; + mfrs_info->pid = pid; + + ret = of_property_read_string(np, "pd,mfrs", &mstring); + if (ret < 0) { + pr_err("%s get pd mfrs fail\n", __func__); + mstring = "no_pd_mfrs_string"; + } + ret = snprintf(mfrs_info->mfrs_string, + sizeof(mfrs_info->mfrs_string), "%s", mstring); + if (ret < 0 || ret >= sizeof(mfrs_info->mfrs_string)) + pr_info("%s-%d snprintf fail\n", __func__, __LINE__); + + pr_info("%s PD mfrs_string = %s\n", + __func__, mfrs_info->mfrs_string); +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + pd_port->src_cap_ext.vid = vid; + pd_port->src_cap_ext.pid = pid; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + + pd_port->id_vdos[0] &= ~PD_IDH_VID_MASK; + pd_port->id_vdos[0] |= PD_IDH_VID(vid); + + pd_port->id_vdos[2] = VDO_PRODUCT( + pid, PD_PRODUCT_BCD(pd_port->id_vdos[2])); + + pd_port->id_header = pd_port->id_vdos[0]; +} + +static int pd_parse_pdata(struct pd_port *pd_port) +{ + u32 val; + struct device_node *np; + int ret = 0, i; + + pr_info("%s\n", __func__); + np = of_find_node_by_name(pd_port->tcpc->dev.of_node, "pd-data"); + + if (np) { + ret = of_property_read_u32(np, "pd,source-pdo-size", + (u32 *)&pd_port->local_src_cap_default.nr); + if (ret < 0) + pr_err("%s get source pdo size fail\n", __func__); + + ret = of_property_read_u32_array(np, "pd,source-pdo-data", + (u32 *)pd_port->local_src_cap_default.pdos, + pd_port->local_src_cap_default.nr); + if (ret < 0) + pr_err("%s get source pdo data fail\n", __func__); + + pr_info("%s src pdo data =\n", __func__); + for (i = 0; i < pd_port->local_src_cap_default.nr; i++) { + pr_info("%s %d: 0x%08x\n", __func__, i, + pd_port->local_src_cap_default.pdos[i]); + } + + ret = of_property_read_u32(np, "pd,sink-pdo-size", + (u32 *)&pd_port->local_snk_cap.nr); + if (ret < 0) + pr_err("%s get sink pdo size fail\n", __func__); + + ret = of_property_read_u32_array(np, "pd,sink-pdo-data", + (u32 *)pd_port->local_snk_cap.pdos, + pd_port->local_snk_cap.nr); + if (ret < 0) + pr_err("%s get sink pdo data fail\n", __func__); + + pr_info("%s snk pdo data =\n", __func__); + for (i = 0; i < pd_port->local_snk_cap.nr; i++) { + pr_info("%s %d: 0x%08x\n", __func__, i, + pd_port->local_snk_cap.pdos[i]); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (PDO_TYPE(pd_port->local_snk_cap.pdos[i]) != + PDO_TYPE_APDO) + pd_port->local_snk_cap_nr_pd20++; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + } + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + pd_port->local_snk_cap_nr_pd30 = pd_port->local_snk_cap.nr; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + ret = of_property_read_u32(np, "pd,id-vdo-size", + (u32 *)&pd_port->id_vdo_nr); + if (ret < 0) + pr_err("%s get id vdo size fail\n", __func__); + ret = of_property_read_u32_array(np, "pd,id-vdo-data", + (u32 *)pd_port->id_vdos, pd_port->id_vdo_nr); + if (ret < 0) + pr_err("%s get id vdo data fail\n", __func__); + + pr_info("%s id vdos data =\n", __func__); + for (i = 0; i < pd_port->id_vdo_nr; i++) + pr_info("%s %d: 0x%08x\n", __func__, i, + pd_port->id_vdos[i]); + + pd_port->id_header = pd_port->id_vdos[0]; + + val = DPM_CHARGING_POLICY_MAX_POWER_LVIC; + if (of_property_read_u32(np, "pd,charging_policy", &val) < 0) + pr_info("%s get charging policy fail\n", __func__); + + pd_port->dpm_charging_policy = val; + pd_port->dpm_charging_policy_default = val; + pr_info("%s charging_policy = %d\n", __func__, val); + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO + ret = pd_parse_pdata_bats(pd_port, np); + if (ret < 0) + return ret; +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY + ret = pd_parse_pdata_countries(pd_port, np); + if (ret < 0) + return ret; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_AUTHORITY */ + + pd_parse_pdata_src_cap_ext(pd_port, np); + pd_parse_pdata_mfrs(pd_port, np); + } + + return 0; +} + +static const struct { + const char *prop_name; + uint32_t val; +} supported_dpm_caps[] = { + {"local_dr_power", DPM_CAP_LOCAL_DR_POWER}, + {"local_dr_data", DPM_CAP_LOCAL_DR_DATA}, + {"local_ext_power", DPM_CAP_LOCAL_EXT_POWER}, + {"local_usb_comm", DPM_CAP_LOCAL_USB_COMM}, + {"local_usb_suspend", DPM_CAP_LOCAL_USB_SUSPEND}, + {"local_high_cap", DPM_CAP_LOCAL_HIGH_CAP}, + {"local_give_back", DPM_CAP_LOCAL_GIVE_BACK}, + {"local_no_suspend", DPM_CAP_LOCAL_NO_SUSPEND}, + {"local_vconn_supply", DPM_CAP_LOCAL_VCONN_SUPPLY}, + + {"attemp_discover_cable_dfp", DPM_CAP_ATTEMP_DISCOVER_CABLE_DFP}, + {"attemp_enter_dp_mode", DPM_CAP_ATTEMP_ENTER_DP_MODE}, + {"attemp_discover_cable", DPM_CAP_ATTEMP_DISCOVER_CABLE}, + {"attemp_discover_id", DPM_CAP_ATTEMP_DISCOVER_ID}, + {"attemp_discover_svid", DPM_CAP_ATTEMP_DISCOVER_SVID}, + + {"pr_reject_as_source", DPM_CAP_PR_SWAP_REJECT_AS_SRC}, + {"pr_reject_as_sink", DPM_CAP_PR_SWAP_REJECT_AS_SNK}, + {"pr_check_gp_source", DPM_CAP_PR_SWAP_CHECK_GP_SRC}, + {"pr_check_gp_sink", DPM_CAP_PR_SWAP_CHECK_GP_SNK}, + + {"dr_reject_as_dfp", DPM_CAP_DR_SWAP_REJECT_AS_DFP}, + {"dr_reject_as_ufp", DPM_CAP_DR_SWAP_REJECT_AS_UFP}, +}; + +static void pd_core_power_flags_init(struct pd_port *pd_port) +{ + uint32_t src_flag, snk_flag, val; + struct device_node *np; + int i; + struct pd_port_power_caps *snk_cap = &pd_port->local_snk_cap; + struct pd_port_power_caps *src_cap = + &pd_port->local_src_cap_default; + + np = of_find_node_by_name(pd_port->tcpc->dev.of_node, "dpm_caps"); + + for (i = 0; i < ARRAY_SIZE(supported_dpm_caps); i++) { + if (of_property_read_bool(np, + supported_dpm_caps[i].prop_name)) + pd_port->dpm_caps |= + supported_dpm_caps[i].val; + pr_info("dpm_caps: %s\n", + supported_dpm_caps[i].prop_name); + } + + if (of_property_read_u32(np, "pr_check", &val) == 0) + pd_port->dpm_caps |= DPM_CAP_PR_CHECK_PROP(val); + else + pr_err("%s get pr_check data fail\n", __func__); + + if (of_property_read_u32(np, "dr_check", &val) == 0) + pd_port->dpm_caps |= DPM_CAP_DR_CHECK_PROP(val); + else + pr_err("%s get dr_check data fail\n", __func__); + + pr_info("dpm_caps = 0x%08x\n", pd_port->dpm_caps); + + src_flag = 0; + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) + src_flag |= PDO_FIXED_DUAL_ROLE; + + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_DATA) + src_flag |= PDO_FIXED_DATA_SWAP; + + if (pd_port->dpm_caps & DPM_CAP_LOCAL_EXT_POWER) + src_flag |= PDO_FIXED_EXTERNAL; + + if (pd_port->dpm_caps & DPM_CAP_LOCAL_USB_COMM) + src_flag |= PDO_FIXED_COMM_CAP; + + snk_flag = src_flag; + + if (pd_port->dpm_caps & DPM_CAP_LOCAL_USB_SUSPEND) + src_flag |= PDO_FIXED_SUSPEND; + + if (pd_port->dpm_caps & DPM_CAP_LOCAL_HIGH_CAP) + snk_flag |= PDO_FIXED_HIGH_CAP; + + snk_cap->pdos[0] |= snk_flag; + src_cap->pdos[0] |= src_flag; +} + +#ifdef CONFIG_RECV_BAT_ABSENT_NOTIFY +static void fg_bat_absent_work(struct work_struct *work) +{ + struct pd_port *pd_port = container_of(work, struct pd_port, + fg_bat_work); + struct tcpc_device *tcpc = pd_port->tcpc; + int ret = 0; + + ret = tcpm_shutdown(tcpc); + if (ret < 0) + pr_notice("%s: tcpm shutdown fail\n", __func__); +} +#endif /* ONFIG_RECV_BAT_ABSENT_NOTIFY */ + +int pd_core_init(struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + int ret; + + mutex_init(&pd_port->pd_lock); + +#ifdef CONFIG_USB_PD_BLOCK_TCPM + mutex_init(&pd_port->tcpm_bk_lock); + init_waitqueue_head(&pd_port->tcpm_bk_wait_que); +#endif /* CONFIG_USB_PD_BLOCK_TCPM */ + + pd_port->tcpc = tcpc; + pd_port->pe_pd_state = PE_IDLE2; + pd_port->cap_miss_match = 0; /* For src_cap miss match */ + + ret = pd_parse_pdata(pd_port); + if (ret < 0) + return ret; + + pd_core_power_flags_init(pd_port); + + pd_dpm_core_init(pd_port); + +#ifdef CONFIG_RECV_BAT_ABSENT_NOTIFY + INIT_WORK(&pd_port->fg_bat_work, fg_bat_absent_work); +#endif /* CONFIG_RECV_BAT_ABSENT_NOTIFY */ + + PE_INFO("%s\n", __func__); + return 0; +} + +void pd_extract_rdo_power(uint32_t rdo, uint32_t pdo, + uint32_t *op_curr, uint32_t *max_curr) +{ + uint32_t op_power, max_power, vmin; + + switch (pdo & PDO_TYPE_MASK) { + case PDO_TYPE_FIXED: + case PDO_TYPE_VARIABLE: + *op_curr = RDO_FIXED_VAR_EXTRACT_OP_CURR(rdo); + *max_curr = RDO_FIXED_VAR_EXTRACT_MAX_CURR(rdo); + break; + + case PDO_TYPE_BATTERY: /* TODO: check it later !! */ + vmin = PDO_BATT_EXTRACT_MIN_VOLT(pdo); + op_power = RDO_BATT_EXTRACT_OP_POWER(rdo); + max_power = RDO_BATT_EXTRACT_MAX_POWER(rdo); + + *op_curr = op_power / vmin; + *max_curr = max_power / vmin; + break; + +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + case PDO_TYPE_APDO: + *op_curr = RDO_APDO_EXTRACT_OP_MA(rdo); + *max_curr = RDO_APDO_EXTRACT_OP_MA(rdo); + break; +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ + + default: + *op_curr = *max_curr = 0; + break; + } +} + +uint32_t pd_reset_pdo_power(struct tcpc_device *tcpc, + uint32_t pdo, uint32_t imax) +{ + uint32_t ioper; + + switch (pdo & PDO_TYPE_MASK) { + case PDO_TYPE_FIXED: + ioper = PDO_FIXED_EXTRACT_CURR(pdo); + if (ioper > imax) + return PDO_FIXED_RESET_CURR(pdo, imax); + break; + + case PDO_TYPE_VARIABLE: + ioper = PDO_VAR_EXTRACT_CURR(pdo); + if (ioper > imax) + return PDO_VAR_RESET_CURR(pdo, imax); + break; + + case PDO_TYPE_BATTERY: + /* TODO: check it later !! */ + PD_ERR("No Support\n"); + break; + +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + case PDO_TYPE_APDO: + /* TODO: check it later !! */ + break; +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ + } + return pdo; +} + +uint32_t pd_get_cable_curr_lvl(struct pd_port *pd_port) +{ + return PD_VDO_CABLE_CURR( + pd_port->pe_data.cable_vdos[VDO_DISCOVER_ID_CABLE]); +} + +uint32_t pd_get_cable_current_limit(struct pd_port *pd_port) +{ + switch (pd_get_cable_curr_lvl(pd_port)) { + case CABLE_CURR_1A5: + return 1500; + case CABLE_CURR_5A: + return 5000; + default: + case CABLE_CURR_3A: + return 3000; + } +} + +static inline bool pd_is_cable_communication_available( + struct pd_port *pd_port) +{ + /* + * After pr_swap or fr_swap, + * the source (must be Vconn SRC) can communicate with Cable, + * the sink doesn't communicate with cable even if it's DFP. + * + * When an Explicit Contract is in place, + * Only the Vconn SRC can communicate with Cable. + */ + +#ifdef CONFIG_USB_PD_REV30_DISCOVER_CABLE_WITH_VCONN + if (pd_check_rev30(pd_port) && (!pd_port->vconn_role)) + return false; +#endif /* CONFIG_USB_PD_REV30_DISCOVER_CABLE_WITH_VCONN */ + + return true; +} + +bool pd_is_reset_cable(struct pd_port *pd_port) +{ + if (!dpm_reaction_check(pd_port, DPM_REACTION_CAP_RESET_CABLE)) + return false; + + return pd_is_cable_communication_available(pd_port); +} + +bool pd_is_discover_cable(struct pd_port *pd_port) +{ + if (!dpm_reaction_check(pd_port, DPM_REACTION_CAP_DISCOVER_CABLE)) + return false; + + if (pd_port->pe_data.discover_id_counter >= PD_DISCOVER_ID_COUNT) { + dpm_reaction_clear(pd_port, + DPM_REACTION_DISCOVER_CABLE | + DPM_REACTION_CAP_DISCOVER_CABLE); + return false; + } + + return pd_is_cable_communication_available(pd_port); +} + +void pd_reset_svid_data(struct pd_port *pd_port) +{ + uint8_t i; + struct svdm_svid_data *svid_data; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + svid_data->exist = false; + svid_data->remote_mode.mode_cnt = 0; + svid_data->active_mode = 0; + } +} + +#define PE_RESET_MSG_ID(pd_port, sop) { \ + pd_port->pe_data.msg_id_tx[sop] = 0; \ + pd_port->pe_data.msg_id_rx[sop] = PD_MSG_ID_MAX; \ +} + +int pd_reset_protocol_layer(struct pd_port *pd_port, bool sop_only) +{ + struct pe_data *pe_data = &pd_port->pe_data; + + pd_port->state_machine = PE_STATE_MACHINE_NORMAL; + + pd_notify_pe_reset_protocol(pd_port); + +#ifdef CONFIG_USB_PD_PE_SOURCE + pe_data->cap_counter = 0; +#endif /* CONFIG_USB_PD_PE_SOURCE */ + + pe_data->explicit_contract = false; + pe_data->local_selected_cap = 0; + pe_data->remote_selected_cap = 0; + pe_data->during_swap = 0; + pd_port->cap_miss_match = 0; + +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + pe_data->remote_alert = 0; +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY_RESET + if (pe_data->pd_prev_connected) + dpm_reaction_set(pd_port, DPM_REACTION_DFP_FLOW_DELAY); +#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY_RESET */ + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID + dpm_reaction_clear(pd_port, DPM_REACTION_RETURN_VCONN_SRC); +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ + + PE_RESET_MSG_ID(pd_port, TCPC_TX_SOP); + + if (!sop_only) { + pd_port->request_i = -1; + pd_port->request_v = TCPC_VBUS_SINK_5V; + + PE_RESET_MSG_ID(pd_port, TCPC_TX_SOP_PRIME); + PE_RESET_MSG_ID(pd_port, TCPC_TX_SOP_PRIME_PRIME); + } + +#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP + pd_port->msg_id_pr_swap_last = 0xff; +#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */ + + return 0; +} + +int pd_set_rx_enable(struct pd_port *pd_port, uint8_t enable) +{ + return tcpci_set_rx_enable(pd_port->tcpc, enable); +} + +int pd_enable_vbus_valid_detection(struct pd_port *pd_port, bool wait_valid) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PE_DBG("WaitVBUS=%d\n", wait_valid); + pd_notify_pe_wait_vbus_once(pd_port, + wait_valid ? PD_WAIT_VBUS_VALID_ONCE : + PD_WAIT_VBUS_INVALID_ONCE); + return 0; +} + +int pd_enable_vbus_safe0v_detection(struct pd_port *pd_port) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PE_DBG("WaitVSafe0V\n"); + pd_notify_pe_wait_vbus_once(pd_port, PD_WAIT_VBUS_SAFE0V_ONCE); + return 0; +} + +int pd_enable_vbus_stable_detection(struct pd_port *pd_port) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PE_DBG("WaitVStable\n"); + pd_notify_pe_wait_vbus_once(pd_port, PD_WAIT_VBUS_STABLE_ONCE); + return 0; +} + +static inline int pd_update_msg_header(struct pd_port *pd_port) +{ + return tcpci_set_msg_header(pd_port->tcpc, + pd_port->power_role, pd_port->data_role); +} + +int pd_set_data_role(struct pd_port *pd_port, uint8_t dr) +{ + int ret = 0; + + if (dr == pd_port->data_role) + return ret; + + pd_port->data_role = dr; + ret = pd_update_msg_header(pd_port); + if (ret < 0) + return ret; + +#ifdef CONFIG_DUAL_ROLE_USB_INTF + /* dual role usb--> 0:ufp, 1:dfp */ + pd_port->tcpc->dual_role_mode = pd_port->data_role; + /* dual role usb --> 0: Device, 1: Host */ + pd_port->tcpc->dual_role_dr = !(pd_port->data_role); + dual_role_instance_changed(pd_port->tcpc->dr_usb); +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + + tcpci_notify_role_swap(pd_port->tcpc, TCP_NOTIFY_DR_SWAP, dr); + return ret; +} + +int pd_set_power_role(struct pd_port *pd_port, uint8_t pr) +{ + int ret = 0; + + if (pr == pd_port->power_role) + return ret; + + pd_port->power_role = pr; + ret = pd_update_msg_header(pd_port); + if (ret < 0) + return ret; + + pd_notify_pe_pr_changed(pd_port); + +#ifdef CONFIG_DUAL_ROLE_USB_INTF + /* 0:sink, 1: source */ + pd_port->tcpc->dual_role_pr = !(pd_port->power_role); + dual_role_instance_changed(pd_port->tcpc->dr_usb); +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + + tcpci_notify_role_swap(pd_port->tcpc, TCP_NOTIFY_PR_SWAP, pr); + return ret; +} + +static void pd_init_spec_revision(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_REV30_SYNC_SPEC_REV + if (pd_port->tcpc->tcpc_flags & TCPC_FLAGS_PD_REV30) { + pd_port->pd_revision[0] = PD_REV30; + pd_port->pd_revision[1] = PD_REV30; + } else { + pd_port->pd_revision[0] = PD_REV20; + pd_port->pd_revision[1] = PD_REV20; + } +#endif /* CONFIG_USB_PD_REV30_SYNC_SPEC_REV */ +} + +int pd_init_message_hdr(struct pd_port *pd_port, bool act_as_sink) +{ + if (act_as_sink) { + pd_port->power_role = PD_ROLE_SINK; + pd_port->data_role = PD_ROLE_UFP; + pd_port->vconn_role = PD_ROLE_VCONN_OFF; + } else { + pd_port->power_role = PD_ROLE_SOURCE; + pd_port->data_role = PD_ROLE_DFP; + pd_port->vconn_role = PD_ROLE_VCONN_ON; + } + + pd_init_spec_revision(pd_port); + return pd_update_msg_header(pd_port); +} + +int pd_set_vconn(struct pd_port *pd_port, uint8_t role) +{ + int ret = 0; + bool enable = !!(role & PD_ROLE_VCONN_ON); + bool en_role = role != PD_ROLE_VCONN_OFF; + bool en_role_old = pd_port->vconn_role != PD_ROLE_VCONN_OFF; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PE_DBG("%s:%d\n", __func__, role); + +#ifdef CONFIG_USB_PD_VCONN_SAFE5V_ONLY + if (pd_port->pe_data.vconn_highv_prot) { + PE_DBG("VC_OVER5V\n"); + return -EPERM; + } +#endif /* CONFIG_USB_PD_VCONN_SAFE5V_ONLY */ + + if (role == pd_port->vconn_role) + goto out; + + pd_port->vconn_role = role; + ret = tcpci_set_vconn(tcpc, enable); + if (ret < 0) + return ret; + + if (en_role != en_role_old) { + tcpci_notify_role_swap(tcpc, TCP_NOTIFY_VCONN_SWAP, en_role); +#ifdef CONFIG_DUAL_ROLE_USB_INTF + tcpc->dual_role_vconn = en_role; + dual_role_instance_changed(tcpc->dr_usb); +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + } +out: +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY + if (role == PD_ROLE_VCONN_DYNAMIC_ON) + pd_restart_timer(pd_port, PD_TIMER_VCONN_STABLE); +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + + if (!enable) + PE_RESET_MSG_ID(pd_port, TCPC_TX_SOP_PRIME); + + return ret; +} + +static inline int pd_reset_modal_operation(struct pd_port *pd_port) +{ + uint8_t i; + struct svdm_svid_data *svid_data; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + + if (svid_data->active_mode) { + svid_data->active_mode = 0; + tcpci_exit_mode(pd_port->tcpc, svid_data->svid); + } + } + + pd_port->pe_data.modal_operation = false; + return 0; +} + +int pd_reset_local_hw(struct pd_port *pd_port) +{ + uint8_t dr; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + pd_notify_pe_transit_to_default(pd_port); + pd_unlock_msg_output(pd_port); + + pd_reset_pe_timer(pd_port); + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_HARDRESET); + + pd_port->pe_data.explicit_contract = false; + pd_port->pe_data.pd_connected = false; + pd_port->pe_data.pe_ready = false; + +#ifdef CONFIG_USB_PD_VCONN_SAFE5V_ONLY + pd_port->pe_data.vconn_highv_prot = false; + pd_port->pe_data.vconn_highv_prot_role = PD_ROLE_VCONN_OFF; +#endif /* CONFIG_USB_PD_VCONN_SAFE5V_ONLY */ + +#ifdef CONFIG_USB_PD_RESET_CABLE + dpm_reaction_clear(pd_port, DPM_REACTION_CAP_RESET_CABLE); +#endif /* CONFIG_USB_PD_RESET_CABLE */ + + pd_reset_modal_operation(pd_port); + + pd_set_vconn(pd_port, PD_ROLE_VCONN_OFF); + + if (pd_port->power_role == PD_ROLE_SINK) + dr = PD_ROLE_UFP; + else + dr = PD_ROLE_DFP; + + pd_port->state_machine = PE_STATE_MACHINE_NORMAL; + + pd_set_data_role(pd_port, dr); + pd_init_spec_revision(pd_port); + pd_dpm_notify_pe_hardreset(pd_port); + PE_DBG("reset_local_hw\n"); + + return 0; +} + +int pd_enable_bist_test_mode(struct pd_port *pd_port, bool en) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PE_DBG("bist_test_mode=%d\n", en); + return tcpci_set_bist_test_mode(tcpc, en); +} + +/* ---- Handle PD Message ----*/ + +int pd_handle_soft_reset(struct pd_port *pd_port) +{ + PE_STATE_RECV_SOFT_RESET(pd_port); + + pd_reset_protocol_layer(pd_port, true); + pd_notify_tcp_event_buf_reset(pd_port, TCP_DPM_RET_DROP_RECV_SRESET); + return pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT); +} + +void pd_handle_first_pd_command(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_REV30 + pd_sync_sop_spec_revision(pd_port); +#endif /* CONFIG_USB_PD_REV30 */ + + pd_port->pe_data.pd_connected = true; + pd_port->pe_data.pd_prev_connected = true; +} + +void pd_handle_hard_reset_recovery(struct pd_port *pd_port) +{ + /* Stop NoResponseTimer and reset HardResetCounter to zero */ + pd_port->pe_data.hard_reset_counter = 0; + pd_disable_timer(pd_port, PD_TIMER_NO_RESPONSE); + +#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER + pd_port->pe_data.renegotiation_count++; +#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */ + +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER + pd_port->pe_data.recv_hard_reset_count = 0; +#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + + pd_notify_pe_hard_reset_completed(pd_port); +} + +/* ---- Send PD Message ----*/ + +int pd_send_message(struct pd_port *pd_port, uint8_t sop_type, + uint8_t msg, bool ext, uint16_t count, const uint32_t *data) +{ + int ret; + uint8_t msg_id; + uint16_t msg_hdr; + uint16_t msg_hdr_private; + uint8_t pd_rev = pd_get_rev(pd_port, sop_type); + uint8_t type = PD_TX_STATE_WAIT_CRC_PD; + struct pe_data *pe_data = &pd_port->pe_data; + struct tcpc_device *tcpc = pd_port->tcpc; + + if (tcpc->typec_attach_old == 0) { + PE_DBG("[SendMsg] Unattached\n"); + return 0; + } + + if (tcpc->pd_hard_reset_event_pending) { + PE_DBG("[SendMsg] HardReset Pending"); + return 0; + } + + if (sop_type == TCPC_TX_SOP) + msg_hdr_private = PD_HEADER_ROLE( + pd_port->power_role, pd_port->data_role); + else + msg_hdr_private = 0; + +#ifdef CONFIG_USB_PD_REV30 + if (pd_rev >= PD_REV30) + tcpc->pd_retry_count = PD30_RETRY_COUNT; + else + tcpc->pd_retry_count = PD_RETRY_COUNT; +#endif /* CONFIG_USB_PD_REV30 */ + + msg_id = pe_data->msg_id_tx[sop_type]; + msg_hdr = PD_HEADER_COMMON( + msg, pd_rev, msg_id, count, ext, msg_hdr_private); + + /* ext-cmd 15 is reserved */ + if ((count > 0) && (msg == PD_DATA_VENDOR_DEF)) + type = PD_TX_STATE_WAIT_CRC_VDM; + + pe_data->msg_id_tx[sop_type] = (msg_id+1) % PD_MSG_ID_MAX; + + pd_notify_pe_transmit_msg(pd_port, type); + ret = tcpci_transmit(pd_port->tcpc, sop_type, msg_hdr, data); + if (ret < 0) + PD_ERR("[SendMsg] Failed, %d\n", ret); + + return ret; +} + +int pd_send_data_msg(struct pd_port *pd_port, + uint8_t sop_type, uint8_t msg, uint8_t cnt, uint32_t *payload) +{ + return pd_send_message(pd_port, + sop_type, msg, false, cnt, payload); +} + +int pd_send_sop_ctrl_msg( + struct pd_port *pd_port, uint8_t msg) +{ + return pd_send_message( + pd_port, TCPC_TX_SOP, msg, false, 0, NULL); +} + +int pd_send_sop_prime_ctrl_msg(struct pd_port *pd_port, uint8_t msg) +{ + return pd_send_message( + pd_port, TCPC_TX_SOP_PRIME, msg, false, 0, NULL); +} + +int pd_send_sop_data_msg(struct pd_port *pd_port, + uint8_t msg, uint8_t cnt, const uint32_t *payload) +{ + return pd_send_message(pd_port, + TCPC_TX_SOP, msg, false, cnt, payload); +} + +int pd_reply_wait_reject_msg(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + return pd_reply_wait_reject_msg_no_resp(pd_port); +} + +int pd_reply_wait_reject_msg_no_resp(struct pd_port *pd_port) +{ + struct pd_event *pd_event = pd_get_curr_pd_event(pd_port); + uint8_t msg = pd_event->msg_sec == PD_DPM_NAK_REJECT ? + PD_CTRL_REJECT : PD_CTRL_WAIT; + + return pd_send_sop_ctrl_msg(pd_port, msg); +} + +#ifdef CONFIG_USB_PD_REV30 +int pd_send_ext_msg(struct pd_port *pd_port, + uint8_t sop_type, uint8_t msg, bool request, + uint8_t chunk_nr, uint8_t size, const uint8_t *data) +{ + uint8_t cnt; + uint32_t payload[PD_DATA_OBJ_SIZE]; + + uint16_t *ext_hdr = (uint16_t *)payload; + + cnt = ((size + PD_EXT_HEADER_PAYLOAD_INDEX - 1) / 4) + 1; + payload[cnt-1] = 0; /* Padding Byte should be 0 */ + + *ext_hdr = PD_EXT_HEADER_CK(size, request, chunk_nr, true); + memcpy(&ext_hdr[1], data, size); + + return pd_send_message(pd_port, sop_type, msg, true, cnt, payload); +} +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_RESET_CABLE +int pd_send_cable_soft_reset(struct pd_port *pd_port) +{ + /* reset_protocol_layer */ + PE_RESET_MSG_ID(pd_port, TCPC_TX_SOP_PRIME); + dpm_reaction_clear(pd_port, DPM_REACTION_CAP_RESET_CABLE); + + return pd_send_sop_prime_ctrl_msg(pd_port, PD_CTRL_SOFT_RESET); +} +#endif /* CONFIG_USB_PD_RESET_CABLE */ + +int pd_send_soft_reset(struct pd_port *pd_port) +{ + PE_STATE_SEND_SOFT_RESET(pd_port); + + pd_reset_protocol_layer(pd_port, true); + pd_notify_tcp_event_buf_reset(pd_port, TCP_DPM_RET_DROP_SENT_SRESET); + return pd_send_sop_ctrl_msg(pd_port, PD_CTRL_SOFT_RESET); +} + +int pd_send_hard_reset(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + PE_DBG("Send HARD Reset\n"); + __pm_wakeup_event(tcpc->attach_wake_lock, 6000); + + pd_port->pe_data.hard_reset_counter++; + pd_notify_pe_send_hard_reset(pd_port); + + return tcpci_transmit(tcpc, TCPC_TX_HARD_RESET, 0, NULL); +} + +int pd_send_bist_mode2(struct pd_port *pd_port) +{ + int ret = 0; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + pd_notify_tcp_event_buf_reset(pd_port, TCP_DPM_RET_DROP_SEND_BIST); + +#ifdef CONFIG_USB_PD_TRANSMIT_BIST2 + TCPC_DBG("BIST_MODE_2\n"); + ret = tcpci_transmit(tcpc, TCPC_TX_BIST_MODE_2, 0, NULL); +#else + ret = tcpci_set_bist_carrier_mode(tcpc, 1 << 2); +#endif + + return ret; +} + +int pd_disable_bist_mode2(struct pd_port *pd_port) +{ +#ifndef CONFIG_USB_PD_TRANSMIT_BIST2 + return tcpci_set_bist_carrier_mode( + pd_port->tcpc, 0); +#else + return 0; +#endif +} + +/* ---- Send / Reply VDM Command ----*/ + +int pd_send_svdm_request(struct pd_port *pd_port, + uint8_t sop_type, uint16_t svid, uint8_t vdm_cmd, + uint8_t obj_pos, uint8_t cnt, uint32_t *data_obj, + uint32_t timer_id) +{ +#ifdef CONFIG_USB_PD_STOP_SEND_VDM_IF_RX_BUSY + int rv; + uint32_t alert_status; +#endif /* CONFIG_USB_PD_STOP_SEND_VDM_IF_RX_BUSY */ + + int ret; + uint8_t ver = SVDM_REV10; + uint32_t payload[PD_DATA_OBJ_SIZE]; + + if (cnt > VDO_MAX_NR) { + PD_BUG_ON(1); + return -EINVAL; + } + + if (pd_get_rev(pd_port, sop_type) >= PD_REV30) + ver = SVDM_REV20; + + payload[0] = VDO_S(svid, ver, CMDT_INIT, vdm_cmd, obj_pos); + memcpy(&payload[1], data_obj, sizeof(uint32_t) * cnt); + +#ifdef CONFIG_USB_PD_STOP_SEND_VDM_IF_RX_BUSY + rv = tcpci_get_alert_status(pd_port->tcpc, &alert_status); + if (rv) + return rv; + + if (alert_status & TCPC_REG_ALERT_RX_STATUS) { + PE_DBG("RX Busy, stop send VDM\n"); + return 0; + } +#endif /* CONFIG_USB_PD_STOP_SEND_VDM_IF_RX_BUSY */ + + ret = pd_send_data_msg( + pd_port, sop_type, PD_DATA_VENDOR_DEF, 1+cnt, payload); + + if (ret == 0 && timer_id != 0) + VDM_STATE_RESPONSE_CMD(pd_port, timer_id); + + return ret; +} + +int pd_reply_svdm_request(struct pd_port *pd_port, + uint8_t reply, uint8_t cnt, uint32_t *data_obj) +{ +#ifdef CONFIG_USB_PD_STOP_REPLY_VDM_IF_RX_BUSY + int rv; + uint32_t alert_status; +#endif /* CONFIG_USB_PD_STOP_REPLY_VDM_IF_RX_BUSY */ + uint8_t ver = SVDM_REV10; + uint32_t payload[PD_DATA_OBJ_SIZE]; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PD_BUG_ON(cnt > VDO_MAX_NR); + + if (pd_check_rev30(pd_port)) + ver = SVDM_REV20; + + payload[0] = VDO_REPLY(ver, reply, pd_get_msg_vdm_hdr(pd_port)); + + if (cnt > 0 && cnt <= PD_DATA_OBJ_SIZE - 1) { + PD_BUG_ON(data_obj == NULL); + memcpy(&payload[1], data_obj, sizeof(uint32_t) * cnt); + } + +#ifdef CONFIG_USB_PD_STOP_REPLY_VDM_IF_RX_BUSY + rv = tcpci_get_alert_status(tcpc, &alert_status); + if (rv) + return rv; + + if (alert_status & TCPC_REG_ALERT_RX_STATUS) { + PE_DBG("RX Busy, stop reply VDM\n"); + return 0; + } +#endif /* CONFIG_USB_PD_STOP_REPLY_VDM_IF_RX_BUSY */ + + if (reply != CMDT_RSP_ACK) + PE_INFO("VDM_NAK_BUSY\n"); + else + PE_INFO("VDM_ACK\n"); + + VDM_STATE_REPLY_SVDM_REQUEST(pd_port); + + return pd_send_sop_data_msg(pd_port, + PD_DATA_VENDOR_DEF, 1+cnt, payload); +} + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + +int pd_send_custom_vdm(struct pd_port *pd_port, uint8_t sop_type) +{ + return pd_send_data_msg(pd_port, + sop_type, PD_DATA_VENDOR_DEF, + pd_port->uvdm_cnt, pd_port->uvdm_data); +} + +int pd_reply_custom_vdm(struct pd_port *pd_port, uint8_t sop_type, + uint8_t cnt, uint32_t *payload) +{ + VDM_STATE_REPLY_SVDM_REQUEST(pd_port); + + return pd_send_data_msg(pd_port, + sop_type, PD_DATA_VENDOR_DEF, cnt, payload); +} + +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +void pd_reset_pe_timer(struct pd_port *pd_port) +{ + tcpc_reset_pe_timer(pd_port->tcpc); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (pd_port->request_apdo) { + pd_port->request_apdo = false; + pd_dpm_start_pps_request_thread(pd_port, false); + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +} + +void pd_lock_msg_output(struct pd_port *pd_port) +{ + if (pd_port->msg_output_lock) + return; + pd_port->msg_output_lock = true; + + pd_dbg_info_lock(); +} + +void pd_unlock_msg_output(struct pd_port *pd_port) +{ + if (!pd_port->msg_output_lock) + return; + pd_port->msg_output_lock = false; + + pd_dbg_info_unlock(); +} + +int pd_update_connect_state(struct pd_port *pd_port, uint8_t state) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->pd_connect_state == state) + return 0; + + pd_port->pd_connect_state = state; + PE_INFO("pd_state=%d\n", state); + return tcpci_notify_pd_state(tcpc, state); +} + +#ifdef CONFIG_USB_PD_REV30 + +/* + * Collision Avoidance : check tx ok + */ + +#ifndef MIN +#define MIN(a, b) ((a < b) ? (a) : (b)) +#endif + +void pd_set_sink_tx(struct pd_port *pd_port, uint8_t cc) +{ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (cc == PD30_SINK_TX_OK && + pd_port->pe_data.pd_traffic_control != PD_SINK_TX_OK) { + PE_INFO("sink_tx_ok\n"); + tcpci_lock_typec(tcpc); + tcpci_set_cc(tcpc, cc); + tcpci_unlock_typec(tcpc); + pd_port->pe_data.pd_traffic_control = PD_SINK_TX_OK; + pd_disable_timer(pd_port, PD_TIMER_SINK_TX); + } else if (cc == PD30_SINK_TX_NG && + pd_port->pe_data.pd_traffic_control == PD_SINK_TX_OK) { + PE_INFO("sink_tx_ng\n"); + tcpci_lock_typec(tcpc); + tcpci_set_cc(tcpc, cc); + tcpci_unlock_typec(tcpc); + pd_port->pe_data.pd_traffic_control = PD_SINK_TX_NG; + pd_enable_timer(pd_port, PD_TIMER_SINK_TX); + } +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +} + +void pd_sync_sop_spec_revision(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_REV30_SYNC_SPEC_REV + uint8_t rev = pd_get_msg_hdr_rev(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (!pd_port->pe_data.pd_connected) { + pd_port->pd_revision[0] = MIN(PD_REV30, rev); + pd_port->pd_revision[1] = MIN(pd_port->pd_revision[1], rev); + + PE_INFO("pd_rev=%d\n", pd_port->pd_revision[0]); + } +#endif /* CONFIG_USB_PD_REV30_SYNC_SPEC_REV */ +} + +void pd_sync_sop_prime_spec_revision(struct pd_port *pd_port, uint8_t rev) +{ +#ifdef CONFIG_USB_PD_REV30_SYNC_SPEC_REV + struct pe_data *pe_data = &pd_port->pe_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (!pe_data->cable_rev_discovered) { + pe_data->cable_rev_discovered = true; + pd_port->pd_revision[1] = MIN(pd_port->pd_revision[1], rev); + PE_INFO("cable_rev=%d\n", pd_port->pd_revision[1]); + } +#endif /* CONFIG_USB_PD_REV30_SYNC_SPEC_REV */ +} + +bool pd_is_multi_chunk_msg(struct pd_port *pd_port) +{ + uint16_t size; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_get_msg_hdr_ext(pd_port)) { + size = pd_get_msg_data_size(pd_port); + if (size > MAX_EXTENDED_MSG_CHUNK_LEN) { + PE_INFO("multi_chunk_msg = TRUE (%d)\n", size); + return true; + } + } + + return false; +} + +struct pd_battery_info *pd_get_battery_info( + struct pd_port *pd_port, enum pd_battery_reference ref) +{ +#ifdef CONFIG_USB_PD_REV30_BAT_INFO + if (ref < pd_get_fix_battery_nr(pd_port)) + return &pd_port->fix_bat_info[ref]; +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + + /* TODO: for swap battery */ + return NULL; +} + +#endif /* CONFIG_USB_PD_REV30 */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_dbg_info.c b/drivers/usb/typec/mmi_tcpc/pd_dbg_info.c new file mode 100644 index 000000000000..4a9664a63369 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_dbg_info.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "inc/pd_dbg_info.h" + +#ifdef CONFIG_PD_DBG_INFO + +#define PD_INFO_BUF_SIZE (2048*256) +#define MSG_POLLING_MS 20 + +#define OUT_BUF_MAX (128) +static struct { + int used; + char buf[PD_INFO_BUF_SIZE + 1 + OUT_BUF_MAX]; +} pd_dbg_buffer[2]; + +static struct mutex buff_lock; +static unsigned int using_buf; +static wait_queue_head_t print_out_wait_que; +static atomic_t pending_print_out; +static atomic_t busy = ATOMIC_INIT(0); + +void pd_dbg_info_lock(void) +{ + atomic_inc(&busy); +} + +void pd_dbg_info_unlock(void) +{ + atomic_dec_if_positive(&busy); +} + +static inline bool pd_dbg_print_out(void) +{ + char temp; + int used; + unsigned int index, i; + + mutex_lock(&buff_lock); + index = using_buf; + using_buf ^= 0x01; /* exchange buffer */ + mutex_unlock(&buff_lock); + + used = pd_dbg_buffer[index].used; + + if (used == 0) + return false; + + pd_dbg_buffer[index].buf[used] = '\0'; + + pr_info("///PD dbg info %ud\n", used); + + for (i = 0; i < used; i += OUT_BUF_MAX) { + temp = pd_dbg_buffer[index].buf[OUT_BUF_MAX + i]; + pd_dbg_buffer[index].buf[OUT_BUF_MAX + i] = '\0'; + + while (atomic_read(&busy)) + usleep_range(1000, 2000); + + pr_notice("%s", pd_dbg_buffer[index].buf + i); + pd_dbg_buffer[index].buf[OUT_BUF_MAX + i] = temp; + } + + /* pr_info("PD dbg info///\n"); */ + pd_dbg_buffer[index].used = 0; + msleep(MSG_POLLING_MS); + return true; +} + +static int print_out_thread_fn(void *data) +{ + while (true) { + wait_event(print_out_wait_que, + atomic_read(&pending_print_out) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + do { + atomic_dec_if_positive(&pending_print_out); + } while (pd_dbg_print_out() && !kthread_should_stop()); + } + + return 0; +} + +int pd_dbg_info(const char *fmt, ...) +{ + unsigned int index; + va_list args; + int r; + int used; + u64 ts; + unsigned long rem_usec; + + ts = local_clock(); + rem_usec = do_div(ts, 1000000000) / 1000 / 1000; + va_start(args, fmt); + mutex_lock(&buff_lock); + index = using_buf; + used = pd_dbg_buffer[index].used; + r = snprintf(pd_dbg_buffer[index].buf + used, + PD_INFO_BUF_SIZE - used, "<%5lu.%03lu>", + (unsigned long)ts, rem_usec); + if (r > 0) + used += r; + r = vsnprintf(pd_dbg_buffer[index].buf + used, + PD_INFO_BUF_SIZE - used, fmt, args); + if (r > 0) + used += r; + + if (pd_dbg_buffer[index].used == 0) { + atomic_inc(&pending_print_out); + wake_up(&print_out_wait_que); + } + + pd_dbg_buffer[index].used = used; + mutex_unlock(&buff_lock); + va_end(args); + return r; +} +EXPORT_SYMBOL(pd_dbg_info); + +static struct task_struct *print_out_task; + +int pd_dbg_info_init(void) +{ + pr_info("%s\n", __func__); + mutex_init(&buff_lock); + init_waitqueue_head(&print_out_wait_que); + atomic_set(&pending_print_out, 0); + print_out_task = kthread_run(print_out_thread_fn, NULL, "pd_dbg_info"); + + return 0; +} + +void pd_dbg_info_exit(void) +{ + kthread_stop(print_out_task); + mutex_destroy(&buff_lock); +} + +MODULE_DESCRIPTION("PD Debug Info Module"); +MODULE_AUTHOR("Patrick Chang "); +MODULE_LICENSE("GPL"); + +#endif /* CONFIG_PD_DBG_INFO */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_dpm_alt_mode_dc.c b/drivers/usb/typec/mmi_tcpc/pd_dpm_alt_mode_dc.c new file mode 100644 index 000000000000..c28fe29a8518 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_dpm_alt_mode_dc.c @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * PD Device Policy Manager for Direct Charge + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" +#include "inc/pd_dpm_core.h" +#include "pd_dpm_prv.h" + +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC + +#define RTDC_UVDM_EN_UNLOCK 0x2024 +#define RTDC_UVDM_RECV_EN_UNLOCK 0x4024 +#define RTDC_SVDM_PPS_AUTHORIZATION 0x10 + +#define RTDC_VALID_MODE 0x01 +#define RTDC_UVDM_EN_UNLOCK_SUCCESS 0x01 + +void crcbits(uint32_t data, uint32_t *crc, uint32_t *ppolynomial) +{ + uint32_t i, newbit, newword, rl_crc; + + for (i = 0; i < 32; i++) { + newbit = ((*crc >> 31) ^ ((data >> i) & 1)) & 1; + if (newbit) + newword = *ppolynomial; + else + newword = 0; + rl_crc = (*crc << 1) | newbit; + *crc = rl_crc ^ newword; + } +} + +uint32_t crcwrap(uint32_t V) +{ + uint32_t ret = 0, i, j, bit; + + V = ~V; + for (i = 0; i < 32; i++) { + j = 31 - i; + bit = (V >> i) & 1; + ret |= bit << j; + } + + return ret; +} + +static uint32_t dc_get_random_code(void) +{ + uint32_t num; + + get_random_bytes(&num, sizeof(num)); + return num; +} + +static uint32_t dc_get_authorization_code(uint32_t data) +{ + uint32_t dwpolynomial = 0x04C11DB6, dwCrc = 0xFFFFFFFF; + + crcbits(data, &dwCrc, &dwpolynomial); + dwCrc = crcwrap(dwCrc); + return dwCrc; +} + +static inline bool dc_dfp_send_en_unlock(struct pd_port *pd_port, + uint32_t cmd, uint32_t data0, uint32_t data1) +{ + pd_port->uvdm_cnt = 3; + pd_port->uvdm_wait_resp = true; + + pd_port->uvdm_data[0] = PD_UVDM_HDR(USB_VID_DIRECTCHARGE, cmd); + pd_port->uvdm_data[1] = data0; + pd_port->uvdm_data[2] = data1; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (pd_port->pe_data.dc_pps_mode) { + pd_port->uvdm_data[0] = VDO_S( + USB_VID_DIRECTCHARGE, + SVDM_REV20, + CMDT_INIT, + RTDC_SVDM_PPS_AUTHORIZATION, + 0); + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + return pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_UVDM); +} + +enum pd_dc_dfp_state { + DC_DFP_NONE = 0, + DC_DFP_DISCOVER_ID, + DC_DFP_DISCOVER_SVIDS, + DC_DFP_DISCOVER_MODES, + DC_DFP_ENTER_MODE, + DC_DFP_EN_UNLOCK1, + DC_DFP_EN_UNLOCK2, + DC_DFP_OPERATION, + +#ifdef RTDC_TA_EMULATE + DC_UFP_T0, + DC_UFP_T1, + DC_UFP_T2, +#endif + + DC_DFP_STATE_NR, + + DC_DFP_ERR = 0X10, + + DC_DFP_ERR_DISCOVER_ID_TYPE, + DC_DFP_ERR_DISCOVER_ID_NAK_TIMEOUT, + + DC_DFP_ERR_DISCOVER_SVID_DC_SID, + DC_DFP_ERR_DISCOVER_SVID_NAK_TIMEOUT, + + DC_DFP_ERR_DISCOVER_CABLE, + + DC_DFP_ERR_DISCOVER_MODE_DC_SID, + DC_DFP_ERR_DISCOVER_MODE_CAP, + DC_DFP_ERR_DISCOVER_MODE_NAK_TIMEROUT, + + DC_DFP_ERR_ENTER_MODE_DC_SID, + DC_DFP_ERR_ENTER_MODE_NAK_TIMEOUT, + + DC_DFP_ERR_EXIT_MODE_DC_SID, + DC_DFP_ERR_EXIT_MODE_NAK_TIMEOUT, + + DC_DFP_ERR_EN_UNLOCK1_FAILED, + DC_DFP_ERR_EN_UNLOCK1_NAK_TIMEOUT, + + DC_DFP_ERR_EN_UNLOCK2_FAILED, + DC_DFP_ERR_EN_UNLOCK2_NAK_TIMEOUT, + + DC_DFP_ERR_PD_REV30, +}; + +#if DC_DBG_ENABLE +static const char * const dc_dfp_state_name[] = { + "dc_dfp_none", + "dc_dfp_discover_id", + "dc_dfp_discover_svids", + "dc_dfp_discover_modes", + "dc_dfp_enter_mode", + "dc_dfp_en_unlock1", + "dc_dfp_en_unlock2", + "dc_dfp_operation", + +#ifdef RTDC_TA_EMULATE + "dc1", + "dc2", + "dc3", +#endif + +}; +#endif /* DC_DBG_ENABLE */ + +void dc_dfp_set_state(struct pd_port *pd_port, uint8_t state) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + pd_port->dc_dfp_state = state; + + if (pd_port->dc_dfp_state < DC_DFP_STATE_NR) + DC_DBG("%s\n", dc_dfp_state_name[state]); + else + DC_DBG("dc_dfp_stop (%d)\n", state); +} + +bool dc_dfp_start_en_unlock1(struct pd_port *pd_port) +{ + uint32_t rn_code[2]; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + rn_code[0] = dc_get_random_code(); + rn_code[1] = dc_get_random_code(); + pd_port->dc_pass_code = dc_get_authorization_code( + (rn_code[0] & 0xffff) | (rn_code[1] & 0xffff0000)); + + DC_DBG("en_unlock1: 0x%x, 0x%x\n", rn_code[0], rn_code[1]); + + dc_dfp_send_en_unlock( + pd_port, RTDC_UVDM_EN_UNLOCK, rn_code[0], rn_code[1]); + + dc_dfp_set_state(pd_port, DC_DFP_EN_UNLOCK1); + + return true; +} + +#define SVDM_CMD_STATE_MASK(raw) (raw & (0x80df)) +#define SVDM_CMD_STATE(cmd, cmd_type) \ + ((1 << 15) | (cmd & 0x1f) | ((cmd_type & 0x03) << 6)) + +bool dc_dfp_verify_en_unlock1(struct pd_port *pd_port) +{ + uint32_t resp_cmd, expect_resp; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + expect_resp = RTDC_UVDM_RECV_EN_UNLOCK; + resp_cmd = PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (pd_port->pe_data.dc_pps_mode) { + resp_cmd = SVDM_CMD_STATE_MASK(pd_port->uvdm_data[0]); + expect_resp = SVDM_CMD_STATE( + RTDC_SVDM_PPS_AUTHORIZATION, CMDT_RSP_ACK); + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + if (resp_cmd != expect_resp) { + DC_INFO("en_unlock1: unexpect resp (0x%x)\n", resp_cmd); + dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK1_FAILED); + return false; + } + + if (pd_port->dc_pass_code != pd_port->uvdm_data[1]) { + DC_INFO("en_unlock1: pass wrong 0x%x 0x%x\n", + pd_port->dc_pass_code, pd_port->uvdm_data[1]); + dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK1_FAILED); + return false; + } + + return true; +} + + +bool dc_dfp_start_en_unlock2(struct pd_port *pd_port) +{ + uint32_t rn_code = dc_get_random_code(); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + pd_port->dc_pass_code = + dc_get_authorization_code(pd_port->uvdm_data[2]); + + DC_DBG("en_unlock2: 0x%x, 0x%x\n", pd_port->dc_pass_code, rn_code); + + dc_dfp_send_en_unlock(pd_port, RTDC_UVDM_EN_UNLOCK, + pd_port->dc_pass_code, rn_code); + + dc_dfp_set_state(pd_port, DC_DFP_EN_UNLOCK2); + + return true; +} + +bool dc_dfp_verify_en_unlock2(struct pd_port *pd_port) +{ + uint32_t resp_cmd, expect_resp; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + expect_resp = RTDC_UVDM_RECV_EN_UNLOCK; + resp_cmd = PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (pd_port->pe_data.dc_pps_mode) { + resp_cmd = SVDM_CMD_STATE_MASK(pd_port->uvdm_data[0]); + expect_resp = SVDM_CMD_STATE( + RTDC_SVDM_PPS_AUTHORIZATION, CMDT_RSP_ACK); + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + if (resp_cmd != expect_resp) { + DC_INFO("en_unlock2: unexpect resp (0x%x)\n", resp_cmd); + dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK2_FAILED); + return false; + } + + if (pd_port->uvdm_data[1] != RTDC_UVDM_EN_UNLOCK_SUCCESS) { + DC_INFO("en_unlock2: failed\n"); + dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK2_FAILED); + return false; + } + + return true; +} + +bool dc_dfp_notify_pe_startup( + struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + if (!(pd_port->id_vdos[0] & PD_IDH_MODAL_SUPPORT)) + return false; + + if (pd_port->dpm_caps & DPM_CAP_ATTEMP_ENTER_DC_MODE) + dc_dfp_set_state(pd_port, DC_DFP_DISCOVER_ID); + +#ifdef RTDC_TA_EMULATE + dc_dfp_set_state(pd_port, DC_UFP_T0); +#endif + + return true; +} + +int dc_dfp_notify_pe_ready(struct pd_port *pd_port, + struct svdm_svid_data *svid_data) +{ +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +#ifdef RTDC_TA_EMULATE + if (pd_port->data_role == PD_ROLE_DFP && svid_data->exist) { + pd_put_tcp_pd_event(pd_port, TCP_DPM_EVT_DR_SWAP_AS_UFP); + return 1; + } else { + return 0; + } +#endif + + if (pd_port->data_role != PD_ROLE_DFP) + return 0; + + if (pd_port->dc_dfp_state != DC_DFP_DISCOVER_MODES) + return 0; + +#ifdef CONFIG_USB_PD_RTDC_CHECK_CABLE + if (!pd_port->pe_data.power_cable_present) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_CABLE); + return 0; + } + + if (pd_get_cable_curr_lvl(pd_port) != CABLE_CURR_5A) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_CABLE); + return 0; + } +#endif /* CONFIG_USB_PD_RTDC_CHECK_CABLE */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + /* If TA support pd revision30, using standard PPS flow */ + if (pd_check_rev30(pd_port)) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_PD_REV30); + return 0; + } + + if (pd_is_source_support_apdo(pd_port)) { + DC_INFO("pps_mode\n"); + pd_port->pe_data.dc_pps_mode = true; + return dc_dfp_start_en_unlock1(pd_port); + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + pd_port->mode_svid = USB_VID_DIRECTCHARGE; + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_DISCOVER_MODES); + return 1; +} + +bool dc_dfp_notify_discover_id(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack) +{ + if (pd_port->dc_dfp_state != DC_DFP_DISCOVER_ID) + return true; + + if (!ack) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_ID_NAK_TIMEOUT); + return true; + } + + /* TODO: Check device ID or Type */ + + dc_dfp_set_state(pd_port, DC_DFP_DISCOVER_SVIDS); + return true; +} + +bool dc_dfp_notify_discover_svid(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack) +{ + if (pd_port->dc_dfp_state != DC_DFP_DISCOVER_SVIDS) + return false; + + if (!ack) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_SVID_NAK_TIMEOUT); + return false; + } + + if (!svid_data->exist) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_SVID_DC_SID); + return false; + } + + dpm_reaction_set(pd_port, DPM_REACTION_DISCOVER_CABLE_FLOW); + dc_dfp_set_state(pd_port, DC_DFP_DISCOVER_MODES); + return true; +} + +bool dc_dfp_notify_discover_modes(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack) +{ + if (pd_port->dc_dfp_state != DC_DFP_DISCOVER_MODES) + return false; + + if (!ack) { + dc_dfp_set_state(pd_port, + DC_DFP_ERR_DISCOVER_MODE_NAK_TIMEROUT); + return false; + } + + if (svid_data->remote_mode.mode_cnt == 0) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_MODE_DC_SID); + return false; + } + + if (svid_data->remote_mode.mode_vdo[0] != RTDC_VALID_MODE) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_MODE_CAP); + return false; + } + + pd_port->mode_obj_pos = 1; + dc_dfp_set_state(pd_port, DC_DFP_ENTER_MODE); + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_ENTER_MODE); + return true; +} + +bool dc_dfp_notify_enter_mode(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops, bool ack) +{ + if (pd_port->dc_dfp_state != DC_DFP_ENTER_MODE) + return true; + + if (!ack) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_ENTER_MODE_NAK_TIMEOUT); + return true; + } + + if (svid_data->active_mode == 0) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_ENTER_MODE_DC_SID); + return false; + } + + return dc_dfp_start_en_unlock1(pd_port); +} + +bool dc_dfp_notify_exit_mode(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops) +{ + if (pd_port->dc_dfp_state <= DC_DFP_ENTER_MODE) + return false; + + if (svid_data->svid != USB_VID_DIRECTCHARGE) + return false; + + dc_dfp_set_state(pd_port, DC_DFP_NONE); + return true; +} + +static inline bool dc_dfp_notify_en_unlock1(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack) +{ + if (pd_port->dc_dfp_state != DC_DFP_EN_UNLOCK1) + return false; + + if (!ack) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK1_NAK_TIMEOUT); + return false; + } + + if (!dc_dfp_verify_en_unlock1(pd_port)) + return false; + + return dc_dfp_start_en_unlock2(pd_port); +} + +static inline bool dc_dfp_notify_en_unlock2(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack) +{ + if (pd_port->dc_dfp_state != DC_DFP_EN_UNLOCK2) + return false; + + if (!ack) { + dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK2_NAK_TIMEOUT); + return false; + } + + if (!dc_dfp_verify_en_unlock2(pd_port)) + return false; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +#ifdef CONFIG_USB_PD_REV30_SYNC_SPEC_REV + if (pd_port->pe_data.dc_pps_mode) + pd_port->pd_revision[0] = PD_REV30; +#endif /* CONFIG_USB_PD_REV30_SYNC_SPEC_REV */ +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + dc_dfp_set_state(pd_port, DC_DFP_OPERATION); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + /* PPS shoult not use en_unlock to notify system */ + if (pd_port->pe_data.dc_pps_mode) + return true; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + tcpci_dc_notify_en_unlock(pd_port->tcpc); + return true; +} + +bool dc_dfp_notify_uvdm(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack) +{ + switch (pd_port->dc_dfp_state) { + case DC_DFP_EN_UNLOCK1: + dc_dfp_notify_en_unlock1(pd_port, svid_data, ack); + break; + + case DC_DFP_EN_UNLOCK2: + dc_dfp_notify_en_unlock2(pd_port, svid_data, ack); + break; + } + + return true; +} + +bool dc_ufp_notify_uvdm(struct pd_port *pd_port, + struct svdm_svid_data *svid_data) +{ +#ifdef RTDC_TA_EMULATE + uint32_t reply_cmd[3]; + uint32_t recv_code[2], rn_code, pass_code; + + reply_cmd[0] = PD_UVDM_HDR(0x29cf, RTDC_UVDM_EN_UNLOCK); + uint32_t cmd = PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]); + + if (cmd != RTDC_UVDM_EN_UNLOCK) { + DC_INFO("What!?"); + return true; + } + + switch (pd_port->dc_dfp_state) { + case DC_UFP_T0: + { + recv_code[0] = pd_port->uvdm_data[1]; + recv_code[1] = pd_port->uvdm_data[2]; + DC_INFO("T0: recv_code: 0x%x, 0x%x\n", + recv_code[0], recv_code[1]); + + pass_code = dc_get_authorization_code( + (recv_code[0] & 0xffff) | + (recv_code[1] & 0xffff0000)); + + rn_code = dc_get_random_code(); + + pd_port->dc_pass_code = + dc_get_authorization_code(rn_code); + + DC_INFO("T0: reply: 0x%x, 0x%x\n", + rn_code, pd_port->dc_pass_code); + + reply_cmd[1] = pass_code; + reply_cmd[2] = rn_code; + + pd_reply_custom_vdm(pd_port, TCPC_TX_SOP, 3, reply_cmd); + dc_dfp_set_state(pd_port, DC_UFP_T1); + } + break; + + case DC_UFP_T1: + { + recv_code[0] = pd_port->uvdm_data[1]; + recv_code[1] = pd_port->uvdm_data[2]; + DC_INFO("T1: recv_code: 0x%x, 0x%x\n", + recv_code[0], recv_code[1]); + + if (recv_code[0] != pd_port->dc_pass_code) { + DC_INFO("T1: pass_code error\n"); + reply_cmd[1] = 0; + } else { + DC_INFO("T1: pass_code success\n"); + reply_cmd[1] = 1; + } + + reply_cmd[2] = dc_get_random_code(); + pd_reply_custom_vdm(pd_port, TCPC_TX_SOP, 3, reply_cmd); + dc_dfp_set_state(pd_port, DC_UFP_T2); + } + break; + } +#endif /* RTDC_TA_EMULATE */ + return true; +} + +bool dc_reset_state(struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + dc_dfp_set_state(pd_port, DC_DFP_NONE); + return true; +} + +bool dc_parse_svid_data(struct pd_port *pd_port, + struct svdm_svid_data *svid_data) +{ + svid_data->local_mode.mode_cnt = 1; + svid_data->local_mode.mode_vdo[0] = 0x00; + pd_port->dpm_caps |= DPM_CAP_ATTEMP_ENTER_DC_MODE; + return true; +} +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ +#endif /* CONFIG_USB_POWER_DELIVERY */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_dpm_alt_mode_dp.c b/drivers/usb/typec/mmi_tcpc/pd_dpm_alt_mode_dp.c new file mode 100644 index 000000000000..db113ff45fc1 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_dpm_alt_mode_dp.c @@ -0,0 +1,1112 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * PD Device Policy Manager for DisplayPort + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" +#include "inc/pd_dpm_core.h" +#include "pd_dpm_prv.h" + +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_USB_PD_ALT_MODE + +/* Display Port DFP_U / UFP_U */ + + +/* DP_Role : DFP_D & UFP_D Both or DFP_D only */ + +#define DP_CHECK_DP_CONNECTED_MATCH(a, b) \ + ((a|b) == DPSTS_BOTH_CONNECTED) + +#define DP_DFP_U_CHECK_ROLE_CAP_MATCH(a, b) \ + ((MODE_DP_PORT_CAP(a)|MODE_DP_PORT_CAP(b)) == MODE_DP_BOTH) + +#define DP_SELECT_CONNECTED(b) ((b == DPSTS_DFP_D_CONNECTED) ? \ + DPSTS_UFP_D_CONNECTED : DPSTS_DFP_D_CONNECTED) + +/* + * If we support ufp_d & dfp_d both, we should choose another role. + * If we don't support both, check dp_connected valid or not + */ +static inline bool dp_update_dp_connected_one(struct pd_port *pd_port, + uint32_t dp_connected, uint32_t dp_local_connected) +{ + bool valid_connected; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + if (dp_local_connected != DPSTS_BOTH_CONNECTED) { + valid_connected = DP_CHECK_DP_CONNECTED_MATCH( + dp_connected, dp_local_connected); + } else { + valid_connected = true; + dp_data->local_status = DP_SELECT_CONNECTED(dp_connected); + } + + return valid_connected; +} + +/* + * If we support ufp_d & dfp_d both, we should decide to use which role. + * For dfp_u, the dp_connected is invalid, and re-send dp_status. + * For ufp_u, the dp_connected is valid, and wait for dp_status from dfp_u + * + * If we don't support both, the dp_connected always is valid + * + */ + +static inline bool dp_update_dp_connected_both(struct pd_port *pd_port, + uint32_t dp_local_connected, bool both_connected_valid) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + bool valid_connected = true; + + if (dp_local_connected == DPSTS_BOTH_CONNECTED) { + dp_data->local_status = pd_port->dp_second_connected; + valid_connected = both_connected_valid; + } + + return valid_connected; +} + +/* DP : DFP_U */ +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +#if DP_DBG_ENABLE +static const char * const dp_dfp_u_state_name[] = { + "dp_dfp_u_none", + "dp_dfp_u_discover_id", + "dp_dfp_u_discover_svids", + "dp_dfp_u_discover_modes", + "dp_dfp_u_enter_mode", + "dp_dfp_u_status_update", + "dp_dfp_u_wait_attention", + "dp_dfp_u_configure", + "dp_dfp_u_operation", +}; +#endif /* DP_DBG_ENABLE */ + +void dp_dfp_u_set_state(struct pd_port *pd_port, uint8_t state) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + dp_data->dfp_u_state = state; + + if (dp_data->dfp_u_state < DP_DFP_U_STATE_NR) + DP_DBG("%s\n", dp_dfp_u_state_name[state]); + else + DP_DBG("dp_dfp_u_stop (%d)\n", state); +} + +bool dp_dfp_u_notify_pe_startup( + struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + if (!(pd_port->id_vdos[0] & PD_IDH_MODAL_SUPPORT)) + return true; + + if (pd_port->dpm_caps & DPM_CAP_ATTEMP_ENTER_DP_MODE) + dp_dfp_u_set_state(pd_port, DP_DFP_U_DISCOVER_ID); + + return true; +} + +int dp_dfp_u_notify_pe_ready( + struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_DBG("%s\n", __func__); + + if (pd_port->data_role != PD_ROLE_DFP) + return 0; + + if (dp_data->dfp_u_state != DP_DFP_U_DISCOVER_MODES) + return 0; + + /* Check Cable later */ + pd_port->mode_svid = USB_SID_DISPLAYPORT; + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_DISCOVER_MODES); + return 1; +} + +bool dp_notify_pe_shutdown( + struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + if (svid_data->active_mode) { + pd_send_vdm_exit_mode(pd_port, TCPC_TX_SOP, + svid_data->svid, svid_data->active_mode); + } + + return true; +} + +bool dp_dfp_u_notify_discover_id(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + uint32_t *payload = pd_get_msg_data_payload(pd_port); + + if (dp_data->dfp_u_state != DP_DFP_U_DISCOVER_ID) + return true; + + if (!ack) { + dp_dfp_u_set_state(pd_port, + DP_DFP_U_ERR_DISCOVER_ID_NAK_TIMEOUT); + return true; + } + + if (payload[VDO_INDEX_IDH] & PD_IDH_MODAL_SUPPORT) + dp_dfp_u_set_state(pd_port, DP_DFP_U_DISCOVER_SVIDS); + else + dp_dfp_u_set_state(pd_port, DP_DFP_U_ERR_DISCOVER_ID_TYPE); + + return true; +} + +bool dp_dfp_u_notify_discover_svid( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, bool ack) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + if (dp_data->dfp_u_state != DP_DFP_U_DISCOVER_SVIDS) + return false; + + if (!ack) { + dp_dfp_u_set_state(pd_port, + DP_DFP_U_ERR_DISCOVER_SVID_NAK_TIMEOUT); + return false; + } + + if (!svid_data->exist) { + dp_dfp_u_set_state(pd_port, DP_DFP_U_ERR_DISCOVER_SVID_DP_SID); + return false; + } + + dpm_reaction_set(pd_port, DPM_REACTION_DISCOVER_CABLE_FLOW); + dp_dfp_u_set_state(pd_port, DP_DFP_U_DISCOVER_MODES); + return true; +} + +static inline bool is_dp_v1_cap_valid(uint32_t dp_cap) +{ + if (((dp_cap >> 24) == 0) && ((dp_cap & 0x00ffffff) != 0)) + return true; + return false; +} + +#define DP_RECEPTACLE (1 << 6) + +/* priority : B -> A -> D/F -> C/E */ +static inline int eval_dp_match_score(uint32_t local_mode, + uint32_t remote_mode, uint32_t *local_dp_config, + uint32_t *remote_dp_config) +{ + uint32_t local_pin_assignment = 0, remote_pin_assignment = 0; + uint32_t common_pin_assignment; + bool remote_is_ufp_pin_assignment = false; + bool local_is_dfp_pin_assignment = false; + int score = 0; + + if (!DP_DFP_U_CHECK_ROLE_CAP_MATCH(local_mode, remote_mode)) + return 0; + + if (((local_mode & MODE_DP_BOTH) == 0) || + ((remote_mode & MODE_DP_BOTH) == 0)) + return 0; + + if (local_mode & DP_RECEPTACLE) { + if (remote_mode & DP_RECEPTACLE) { + if (local_mode & MODE_DP_SRC) { + local_pin_assignment = + MODE_DP_PIN_DFP(local_mode); + remote_pin_assignment = + MODE_DP_PIN_UFP(remote_mode); + remote_is_ufp_pin_assignment = true; + local_is_dfp_pin_assignment = true; + } else { + local_pin_assignment = + MODE_DP_PIN_UFP(local_mode); + remote_pin_assignment = + MODE_DP_PIN_DFP(remote_mode); + remote_is_ufp_pin_assignment = false; + local_is_dfp_pin_assignment = false; + } + } else { + /* remote is plug */ + if (local_mode & MODE_DP_SRC) { + local_pin_assignment = + MODE_DP_PIN_DFP(local_mode); + remote_pin_assignment = + MODE_DP_PIN_DFP(remote_mode); + remote_is_ufp_pin_assignment = false; + local_is_dfp_pin_assignment = true; + } + } + } else { + /* local is plug */ + if (remote_mode & DP_RECEPTACLE) { + if (local_mode & MODE_DP_SNK) { + local_pin_assignment = + MODE_DP_PIN_DFP(local_mode); + remote_pin_assignment = + MODE_DP_PIN_DFP(remote_mode); + remote_is_ufp_pin_assignment = false; + } + } + } + + common_pin_assignment = local_pin_assignment & remote_pin_assignment; + if (common_pin_assignment & (MODE_DP_PIN_C | MODE_DP_PIN_E)) { + score = 1; + if (common_pin_assignment & MODE_DP_PIN_E) { + *local_dp_config = local_is_dfp_pin_assignment ? + VDO_DP_DFP_CFG(DP_PIN_ASSIGN_SUPPORT_E, + DP_SIG_DPV13) : VDO_DP_UFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_DPV13); + *remote_dp_config = remote_is_ufp_pin_assignment ? + VDO_DP_UFP_CFG(DP_PIN_ASSIGN_SUPPORT_E, + DP_SIG_DPV13) : VDO_DP_DFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_DPV13); + } else { + *local_dp_config = local_is_dfp_pin_assignment ? + VDO_DP_DFP_CFG(DP_PIN_ASSIGN_SUPPORT_C, + DP_SIG_DPV13) : VDO_DP_UFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_DPV13); + *remote_dp_config = remote_is_ufp_pin_assignment ? + VDO_DP_UFP_CFG(DP_PIN_ASSIGN_SUPPORT_C, + DP_SIG_DPV13) : VDO_DP_DFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_DPV13); + } + } + if (common_pin_assignment & (MODE_DP_PIN_D | MODE_DP_PIN_F)) { + score = 2; + if (common_pin_assignment & MODE_DP_PIN_F) { + *local_dp_config = local_is_dfp_pin_assignment ? + VDO_DP_DFP_CFG(DP_PIN_ASSIGN_SUPPORT_F, + DP_SIG_DPV13) : VDO_DP_UFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_DPV13); + *remote_dp_config = remote_is_ufp_pin_assignment ? + VDO_DP_UFP_CFG(DP_PIN_ASSIGN_SUPPORT_F, + DP_SIG_DPV13) : VDO_DP_DFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_DPV13); + } else { + *local_dp_config = local_is_dfp_pin_assignment ? + VDO_DP_DFP_CFG(DP_PIN_ASSIGN_SUPPORT_D, + DP_SIG_DPV13) : VDO_DP_UFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_DPV13); + *remote_dp_config = remote_is_ufp_pin_assignment ? + VDO_DP_UFP_CFG(DP_PIN_ASSIGN_SUPPORT_D, + DP_SIG_DPV13) : VDO_DP_DFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_DPV13); + } + } + if ((MODE_DP_SIGNAL_SUPPORT(local_mode) & MODE_DP_GEN2) && + (MODE_DP_SIGNAL_SUPPORT(remote_mode) & MODE_DP_GEN2)) { + if (common_pin_assignment & MODE_DP_PIN_A) { + score = 3; + *local_dp_config = local_is_dfp_pin_assignment ? + VDO_DP_DFP_CFG(DP_PIN_ASSIGN_SUPPORT_A, + DP_SIG_DPV13) : VDO_DP_UFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_GEN2); + *remote_dp_config = remote_is_ufp_pin_assignment ? + VDO_DP_UFP_CFG(DP_PIN_ASSIGN_SUPPORT_A, + DP_SIG_DPV13) : VDO_DP_DFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_GEN2); + } + if (common_pin_assignment & MODE_DP_PIN_B) { + score = 4; + *local_dp_config = local_is_dfp_pin_assignment ? + VDO_DP_DFP_CFG(DP_PIN_ASSIGN_SUPPORT_B, + DP_SIG_DPV13) : VDO_DP_UFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_GEN2); + *remote_dp_config = remote_is_ufp_pin_assignment ? + VDO_DP_UFP_CFG(DP_PIN_ASSIGN_SUPPORT_B, + DP_SIG_DPV13) : VDO_DP_DFP_CFG( + DP_PIN_ASSIGN_SUPPORT_E, DP_SIG_GEN2); + } + } + return score; +} + +static inline uint8_t dp_dfp_u_select_mode(struct pd_port *pd_port, + struct dp_data *dp_data, struct svdm_svid_data *svid_data) +{ + uint32_t dp_local_mode, dp_remote_mode, + remote_dp_config = 0, local_dp_config = 0; + struct svdm_mode *remote, *local; + int i, j; + int match_score, best_match_score = 0; + int local_index = -1, remote_index = -1; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + local = &svid_data->local_mode; + remote = &svid_data->remote_mode; + + /* TODO: Evaluate All Modes later ... */ + for (j = 0; j < local->mode_cnt; j++) { + dp_local_mode = local->mode_vdo[j]; + if (!is_dp_v1_cap_valid(dp_local_mode)) + continue; + for (i = 0; i < remote->mode_cnt; i++) { + dp_remote_mode = remote->mode_vdo[i]; + if (!is_dp_v1_cap_valid(dp_remote_mode)) + continue; + match_score = eval_dp_match_score(dp_local_mode, + dp_remote_mode, &local_dp_config, + &remote_dp_config); + if (match_score > best_match_score) { + local_index = j; + remote_index = i; + dp_data->local_config = local_dp_config; + dp_data->remote_config = remote_dp_config; + } + } + } + +#if DP_INFO_ENABLE + for (i = 0; i < svid_data->remote_mode.mode_cnt; i++) { + DP_INFO("Mode%d=0x%08x\n", i, + svid_data->remote_mode.mode_vdo[i]); + } + + DP_INFO("SelectMode:%d\n", remote_index); +#endif /* DP_INFO_ENABLE */ + + /* + * dp_mode = svid_data->remote_mode.mode_vdo[0]; + * dp_local_mode = svid_data->local_mode.mode_vdo[0]; + * cap_match = DP_DFP_U_CHECK_ROLE_CAP_MATCH(dp_mode, dp_local_mode), + * return cap_match ? 1 : 0; + */ + return remote_index + 1; +} + +bool dp_dfp_u_notify_discover_modes( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, bool ack) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dp_data->dfp_u_state != DP_DFP_U_DISCOVER_MODES) + return false; + + if (!ack) { + dp_dfp_u_set_state(pd_port, + DP_DFP_U_ERR_DISCOVER_MODE_NAK_TIMEROUT); + return false; + } + + if (svid_data->remote_mode.mode_cnt == 0) { + dp_dfp_u_set_state(pd_port, DP_DFP_U_ERR_DISCOVER_MODE_DP_SID); + return false; + } + + pd_port->mode_obj_pos = dp_dfp_u_select_mode( + pd_port, dp_data, svid_data); + + if (pd_port->mode_obj_pos == 0) { + DPM_DBG("Can't find match mode\n"); + dp_dfp_u_set_state(pd_port, DP_DFP_U_ERR_DISCOVER_MODE_CAP); + return false; + } + + dp_dfp_u_set_state(pd_port, DP_DFP_U_ENTER_MODE); + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_ENTER_MODE); + return true; +} + +bool dp_dfp_u_notify_enter_mode(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops, bool ack) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + if (dp_data->dfp_u_state != DP_DFP_U_ENTER_MODE) + return true; + + if (!ack) { + dp_dfp_u_set_state(pd_port, + DP_DFP_U_ERR_ENTER_MODE_NAK_TIMEOUT); + return true; + } + + if (svid_data->active_mode == 0) { + dp_dfp_u_set_state(pd_port, DP_DFP_U_ERR_ENTER_MODE_DP_SID); + return false; + } + + dp_data->local_status = pd_port->dp_first_connected; + dp_dfp_u_set_state(pd_port, DP_DFP_U_STATUS_UPDATE); + +#ifdef CONFIG_USB_PD_DBG_DP_DFP_D_AUTO_UPDATE + /* + * For Real Product, + * DFP_U should not send status_update until USB status is changed + * From : "USB Mode, USB Configration" + * To : "DisplayPlay Mode, USB Configration" + * + * After USB status is changed, + * please call following function to continue DFP_U flow. + * tcpm_dpm_dp_status_update(tcpc, 0, 0, NULL) + */ + + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_DP_STATUS_UPDATE); +#endif /* CONFIG_USB_PD_DBG_DP_DFP_D_AUTO_UPDATE */ + + return true; +} + +bool dp_dfp_u_notify_exit_mode( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, uint8_t ops) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + if (dp_data->dfp_u_state <= DP_DFP_U_ENTER_MODE) + return false; + + if (svid_data->svid != USB_SID_DISPLAYPORT) + return false; + + memset(dp_data, 0, sizeof(struct dp_data)); + dp_dfp_u_set_state(pd_port, DP_DFP_U_NONE); + return true; +} + +static inline bool dp_dfp_u_select_pin_mode(struct pd_port *pd_port) +{ + uint32_t dp_local_connected; + uint32_t dp_mode[2], pin_cap[2]; + uint32_t pin_caps, signal; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct svdm_svid_data *svid_data = + dpm_get_svdm_svid_data(pd_port, USB_SID_DISPLAYPORT); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (svid_data == NULL) + return false; + + dp_mode[0] = SVID_DATA_LOCAL_MODE(svid_data, 0); + dp_mode[1] = SVID_DATA_DFP_GET_ACTIVE_MODE(svid_data); + + dp_local_connected = PD_VDO_DPSTS_CONNECT(dp_data->local_status); + + switch (dp_local_connected) { + case DPSTS_DFP_D_CONNECTED: + pin_cap[0] = PD_DP_DFP_D_PIN_CAPS(dp_mode[0]); + pin_cap[1] = PD_DP_UFP_D_PIN_CAPS(dp_mode[1]); + break; + + case DPSTS_UFP_D_CONNECTED: + /* TODO: checkit next version*/ + pin_cap[0] = PD_DP_UFP_D_PIN_CAPS(dp_mode[0]); + pin_cap[1] = PD_DP_DFP_D_PIN_CAPS(dp_mode[1]); + break; + default: + DP_ERR("select_pin error1\n"); + return false; + } + + PE_DBG("modes=0x%x 0x%x\n", dp_mode[0], dp_mode[1]); + PE_DBG("pins=0x%x 0x%x\n", pin_cap[0], pin_cap[1]); + + pin_caps = pin_cap[0] & pin_cap[1]; + + /* if don't want multi-function then ignore those pin configs */ + if (!PD_VDO_DPSTS_MF_PREF(dp_data->remote_status)) + pin_caps &= ~MODE_DP_PIN_MF_MASK; + + /* TODO: If DFP & UFP driver USB Gen2 signal */ + signal = DP_SIG_DPV13; + pin_caps &= ~MODE_DP_PIN_BR2_MASK; + + if (!pin_caps) { + DP_ERR("select_pin error2\n"); + return false; + } + + /* Priority */ + if (pin_caps & MODE_DP_PIN_D) + pin_caps = MODE_DP_PIN_D; + else if (pin_caps & MODE_DP_PIN_F) + pin_caps = MODE_DP_PIN_F; + else if (pin_caps & MODE_DP_PIN_C) + pin_caps = MODE_DP_PIN_C; + else if (pin_caps & MODE_DP_PIN_E) + pin_caps = MODE_DP_PIN_E; + + if (dp_local_connected == DPSTS_DFP_D_CONNECTED) { + dp_data->local_config = VDO_DP_DFP_CFG(pin_caps, signal); + dp_data->remote_config = VDO_DP_UFP_CFG(pin_caps, signal); + } else { + dp_data->local_config = VDO_DP_UFP_CFG(pin_caps, signal); + dp_data->remote_config = VDO_DP_DFP_CFG(pin_caps, signal); + } + + return true; +} + +void dp_dfp_u_request_dp_configuration(struct pd_port *pd_port) +{ + if (!dp_dfp_u_select_pin_mode(pd_port)) { + dp_dfp_u_set_state(pd_port, + DP_DFP_U_ERR_CONFIGURE_SELECT_MODE); + return; + } + + tcpci_dp_notify_config_start(pd_port->tcpc); + + dp_dfp_u_set_state(pd_port, DP_DFP_U_CONFIGURE); + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_DP_CONFIG); +} + +static inline bool dp_dfp_u_update_dp_connected(struct pd_port *pd_port) +{ + bool valid_connected = false; + uint32_t dp_connected, dp_local_connected; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + dp_connected = PD_VDO_DPSTS_CONNECT(dp_data->remote_status); + dp_local_connected = PD_VDO_DPSTS_CONNECT(dp_data->local_status); + + switch (dp_connected) { + case DPSTS_DFP_D_CONNECTED: + case DPSTS_UFP_D_CONNECTED: + valid_connected = dp_update_dp_connected_one( + pd_port, dp_connected, dp_local_connected); + + if (!valid_connected) { + dp_dfp_u_set_state(pd_port, + DP_DFP_U_ERR_STATUS_UPDATE_ROLE); + } + break; + + case DPSTS_DISCONNECT: + dp_dfp_u_set_state(pd_port, DP_DFP_U_WAIT_ATTENTION); + break; + + case DPSTS_BOTH_CONNECTED: + valid_connected = dp_update_dp_connected_both( + pd_port, dp_local_connected, false); + + if (!valid_connected) { + DP_INFO("BOTH_SEL_ONE\n"); + pd_put_tcp_vdm_event(pd_port, + TCP_DPM_EVT_DP_STATUS_UPDATE); + } + break; + } + + return valid_connected; +} + +bool dp_dfp_u_notify_dp_status_update(struct pd_port *pd_port, bool ack) +{ + bool oper_mode = false; + bool valid_connected; + + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + switch (dp_data->dfp_u_state) { + case DP_DFP_U_OPERATION: + oper_mode = true; + case DP_DFP_U_STATUS_UPDATE: + break; + + default: + return false; + } + + if (!ack) { + tcpci_dp_notify_status_update_done(tcpc, 0, false); + dp_dfp_u_set_state(pd_port, + DP_DFP_U_ERR_STATUS_UPDATE_NAK_TIMEOUT); + return false; + } + + if (dpm_vdm_get_svid(pd_port) != USB_SID_DISPLAYPORT) { + dp_dfp_u_set_state(pd_port, DP_DFP_U_ERR_STATUS_UPDATE_DP_SID); + return true; + } + + dp_data->remote_status = pd_get_msg_vdm_data_payload(pd_port)[0]; + DP_INFO("dp_status: 0x%x\n", dp_data->remote_status); + + if (oper_mode) { + tcpci_dp_notify_status_update_done( + tcpc, dp_data->remote_status, ack); + } else { + valid_connected = + dp_dfp_u_update_dp_connected(pd_port); + if (valid_connected) + dp_dfp_u_request_dp_configuration(pd_port); + } + return true; +} + +static inline void dp_ufp_u_auto_update(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_DBG_DP_UFP_U_AUTO_UPDATE + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + if (dp_data->dfp_u_state == DP_DFP_U_OPERATION) + return; + + pd_port->mode_svid = USB_SID_DISPLAYPORT; + dp_data->local_status |= DPSTS_DP_ENABLED | DPSTS_DP_HPD_STATUS; + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_DP_STATUS_UPDATE); +#endif /* CONFIG_USB_PD_DBG_DP_UFP_U_AUTO_UPDATE */ +} + +bool dp_dfp_u_notify_dp_configuration(struct pd_port *pd_port, bool ack) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (ack) { + dp_ufp_u_auto_update(pd_port); + dp_dfp_u_set_state(pd_port, DP_DFP_U_OPERATION); + } else + DP_ERR("config failed: 0x%0x\n", dp_data->remote_config); + + tcpci_dp_notify_config_done(tcpc, + dp_data->local_config, dp_data->remote_config, ack); + + return true; +} + +bool dp_dfp_u_notify_attention(struct pd_port *pd_port, + struct svdm_svid_data *svid_data) +{ + bool valid_connected; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + dp_data->remote_status = pd_get_msg_vdm_data_payload(pd_port)[0]; + + DP_INFO("dp_status: 0x%x\n", dp_data->remote_status); + + switch (dp_data->dfp_u_state) { + case DP_DFP_U_WAIT_ATTENTION: + valid_connected = + dp_dfp_u_update_dp_connected(pd_port); + if (valid_connected) + dp_dfp_u_request_dp_configuration(pd_port); + break; + + case DP_DFP_U_OPERATION: + tcpci_dp_attention(tcpc, dp_data->remote_status); + break; + } + + return true; +} + +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + +/* DP : UFP_U */ + +#if DPM_DBG_ENABLE +static const char * const dp_ufp_u_state_name[] = { + "dp_ufp_u_none", + "dp_ufp_u_startup", + "dp_ufp_u_wait", + "dp_ufp_u_operation", +}; +#endif /* DPM_DBG_ENABLE */ + +static void dp_ufp_u_set_state(struct pd_port *pd_port, uint8_t state) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + dp_data->ufp_u_state = state; + + if (dp_data->ufp_u_state < DP_UFP_U_STATE_NR) + DPM_DBG("%s\n", dp_ufp_u_state_name[state]); + else + DPM_DBG("dp_ufp_u_stop\n"); +} + +void dp_ufp_u_request_enter_mode( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, uint8_t ops) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + dp_data->local_status = pd_port->dp_first_connected; + + if (pd_port->dpm_caps & DPM_CAP_DP_PREFER_MF) + dp_data->local_status |= DPSTS_DP_MF_PREF; + + if (pd_port->dp_first_connected == DPSTS_DISCONNECT) + dp_ufp_u_set_state(pd_port, DP_UFP_U_STARTUP); + else + dp_ufp_u_set_state(pd_port, DP_UFP_U_WAIT); +} + +void dp_ufp_u_request_exit_mode( + struct pd_port *pd_port, struct svdm_svid_data *svid_data, uint8_t ops) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + memset(dp_data, 0, sizeof(struct dp_data)); + dp_ufp_u_set_state(pd_port, DP_UFP_U_NONE); +} + +static inline bool dp_ufp_u_update_dp_connected(struct pd_port *pd_port) +{ + bool valid_connected = false; + uint32_t dp_connected, dp_local_connected; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + dp_connected = PD_VDO_DPSTS_CONNECT(dp_data->remote_status); + dp_local_connected = PD_VDO_DPSTS_CONNECT(dp_data->local_status); + + switch (dp_connected) { + case DPSTS_DFP_D_CONNECTED: + case DPSTS_UFP_D_CONNECTED: + valid_connected = dp_update_dp_connected_one( + pd_port, dp_connected, dp_local_connected); + break; + + case DPSTS_BOTH_CONNECTED: + valid_connected = dp_update_dp_connected_both( + pd_port, dp_local_connected, true); + break; + + default: + break; + } + + return valid_connected; +} + +static inline int dp_ufp_u_request_dp_status(struct pd_port *pd_port) +{ + bool ack; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + dp_data->remote_status = pd_get_msg_vdm_data_payload(pd_port)[0]; + + switch (dp_data->ufp_u_state) { + case DP_UFP_U_WAIT: + ack = dp_ufp_u_update_dp_connected(pd_port); + break; + + case DP_UFP_U_STARTUP: + case DP_UFP_U_OPERATION: + ack = true; + tcpci_dp_status_update( + pd_port->tcpc, dp_data->remote_status); + break; + + default: + ack = false; + break; + } + + if (ack) { + return pd_reply_svdm_request(pd_port, + CMDT_RSP_ACK, 1, &dp_data->local_status); + } else { + return pd_reply_svdm_request_simply(pd_port, CMDT_RSP_NAK); + } +} + +bool dp_ufp_u_is_valid_dp_config(struct pd_port *pd_port, uint32_t dp_config) +{ + /* TODO: Check it later .... */ + uint32_t sel_config; + bool retval = false; + uint32_t local_pin; + struct svdm_svid_data *svid_data = &pd_port->svid_data[0]; + uint32_t local_mode = svid_data->local_mode.mode_vdo[0]; + uint32_t remote_pin = PD_DP_CFG_PIN(dp_config); + + sel_config = MODE_DP_PORT_CAP(dp_config); + switch (sel_config) { + case DP_CONFIG_USB: + retval = true; + break; + + case DP_CONFIG_DFP_D: + local_pin = PD_DP_DFP_D_PIN_CAPS(local_mode); + if ((local_pin & remote_pin) && + (MODE_DP_PORT_CAP(local_mode) & MODE_DP_SRC)) + retval = true; + break; + + case DP_CONFIG_UFP_D: + local_pin = PD_DP_UFP_D_PIN_CAPS(local_mode); + if ((local_pin & remote_pin) && + (MODE_DP_PORT_CAP(local_mode) & MODE_DP_SNK)) + retval = true; + break; + } + + return retval; +} + +static inline void dp_ufp_u_auto_attention(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_DBG_DP_UFP_U_AUTO_ATTENTION + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + pd_port->mode_svid = USB_SID_DISPLAYPORT; + dp_data->local_status |= DPSTS_DP_ENABLED | DPSTS_DP_HPD_STATUS; +#endif /* CONFIG_USB_PD_DBG_DP_UFP_U_AUTO_ATTENTION */ +} + +static inline int dp_ufp_u_request_dp_config(struct pd_port *pd_port) +{ + bool ack = false; + uint32_t dp_config; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + dp_config = pd_get_msg_vdm_data_payload(pd_port)[0]; + DPM_DBG("dp_config: 0x%x\n", dp_config); + + switch (dp_data->ufp_u_state) { + case DP_UFP_U_STARTUP: + case DP_UFP_U_WAIT: + case DP_UFP_U_OPERATION: + ack = dp_ufp_u_is_valid_dp_config(pd_port, dp_config); + + if (ack) { + dp_data->local_config = dp_config; + tcpci_dp_configure(tcpc, dp_config); + dp_ufp_u_auto_attention(pd_port); + dp_ufp_u_set_state(pd_port, DP_UFP_U_OPERATION); + } + break; + } + + return pd_reply_svdm_request_simply( + pd_port, ack ? CMDT_RSP_ACK : CMDT_RSP_NAK); +} + +static inline void dp_ufp_u_send_dp_attention(struct pd_port *pd_port) +{ + struct svdm_svid_data *svid_data; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + switch (dp_data->ufp_u_state) { + case DP_UFP_U_STARTUP: + case DP_UFP_U_OPERATION: + svid_data = dpm_get_svdm_svid_data( + pd_port, USB_SID_DISPLAYPORT); + PD_BUG_ON(svid_data == NULL); + + pd_send_vdm_dp_attention(pd_port, TCPC_TX_SOP, + svid_data->active_mode, dp_data->local_status); + break; + + default: + VDM_STATE_DPM_INFORMED(pd_port); + pd_notify_tcp_vdm_event_2nd_result( + pd_port, TCP_DPM_RET_DENIED_NOT_READY); + break; + } +} + +/* ---- UFP : DP Only ---- */ + +int pd_dpm_ufp_request_dp_status(struct pd_port *pd_port) +{ + return dp_ufp_u_request_dp_status(pd_port); +} + +int pd_dpm_ufp_request_dp_config(struct pd_port *pd_port) +{ + return dp_ufp_u_request_dp_config(pd_port); +} + +void pd_dpm_ufp_send_dp_attention(struct pd_port *pd_port) +{ + dp_ufp_u_send_dp_attention(pd_port); +} + +/* ---- DFP : DP Only ---- */ + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + +void pd_dpm_dfp_send_dp_status_update(struct pd_port *pd_port) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + pd_send_vdm_dp_status(pd_port, TCPC_TX_SOP, + pd_port->mode_obj_pos, 1, &dp_data->local_status); +} + +void pd_dpm_dfp_inform_dp_status_update( + struct pd_port *pd_port, bool ack) +{ + VDM_STATE_DPM_INFORMED(pd_port); + dp_dfp_u_notify_dp_status_update(pd_port, ack); +} + +void pd_dpm_dfp_send_dp_configuration(struct pd_port *pd_port) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + pd_send_vdm_dp_config(pd_port, TCPC_TX_SOP, + pd_port->mode_obj_pos, 1, &dp_data->remote_config); +} + +void pd_dpm_dfp_inform_dp_configuration( + struct pd_port *pd_port, bool ack) +{ + VDM_STATE_DPM_INFORMED(pd_port); + dp_dfp_u_notify_dp_configuration(pd_port, ack); +} + +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + +bool dp_reset_state(struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + memset(dp_data, 0, sizeof(struct dp_data)); + return true; +} + +#define DEFAULT_DP_ROLE_CAP (MODE_DP_SRC) +#define DEFAULT_DP_FIRST_CONNECTED (DPSTS_DFP_D_CONNECTED) +#define DEFAULT_DP_SECOND_CONNECTED (DPSTS_DFP_D_CONNECTED) + +#ifdef CONFIG_USB_PD_ALT_MODE +static const struct { + const char *prop_name; + uint32_t mode; +} supported_dp_pin_modes[] = { + { "pin_assignment,mode_a", MODE_DP_PIN_A }, + { "pin_assignment,mode_b", MODE_DP_PIN_B }, + { "pin_assignment,mode_c", MODE_DP_PIN_C }, + { "pin_assignment,mode_d", MODE_DP_PIN_D }, + { "pin_assignment,mode_e", MODE_DP_PIN_E }, + { "pin_assignment,mode_f", MODE_DP_PIN_F }, +}; + +static const struct { + const char *conn_mode; + uint32_t val; +} dp_connect_mode[] = { + {"both", DPSTS_BOTH_CONNECTED}, + {"dfp_d", DPSTS_DFP_D_CONNECTED}, + {"ufp_d", DPSTS_UFP_D_CONNECTED}, +}; + +bool dp_parse_svid_data( + struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + struct device_node *np, *ufp_np, *dfp_np; + const char *connection; + uint32_t ufp_d_pin_cap = 0; + uint32_t dfp_d_pin_cap = 0; + uint32_t signal = MODE_DP_V13; + uint32_t receptacle = 1; + uint32_t usb2 = 0; + int i = 0; + + np = of_find_node_by_name( + pd_port->tcpc->dev.of_node, "displayport"); + if (np == NULL) { + pr_err("%s get displayport data fail\n", __func__); + return false; + } + + pr_info("dp, svid\n"); + svid_data->svid = USB_SID_DISPLAYPORT; + ufp_np = of_find_node_by_name(np, "ufp_d"); + dfp_np = of_find_node_by_name(np, "dfp_d"); + + if (ufp_np) { + pr_info("dp, ufp_np\n"); + for (i = 0; i < ARRAY_SIZE(supported_dp_pin_modes); i++) { + if (of_property_read_bool(ufp_np, + supported_dp_pin_modes[i].prop_name)) + ufp_d_pin_cap |= + supported_dp_pin_modes[i].mode; + } + } + + if (dfp_np) { + pr_info("dp, dfp_np\n"); + for (i = 0; i < ARRAY_SIZE(supported_dp_pin_modes); i++) { + if (of_property_read_bool(dfp_np, + supported_dp_pin_modes[i].prop_name)) + dfp_d_pin_cap |= + supported_dp_pin_modes[i].mode; + } + } + + if (of_property_read_bool(np, "signal,dp_v13")) + signal |= MODE_DP_V13; + if (of_property_read_bool(np, "signal,dp_gen2")) + signal |= MODE_DP_GEN2; + if (of_property_read_bool(np, "usbr20_not_used")) + usb2 = 1; + if (of_property_read_bool(np, "typec,receptacle")) + receptacle = 1; + + svid_data->local_mode.mode_cnt = 1; + svid_data->local_mode.mode_vdo[0] = VDO_MODE_DP( + ufp_d_pin_cap, dfp_d_pin_cap, + usb2, receptacle, signal, (ufp_d_pin_cap ? MODE_DP_SNK : 0) + | (dfp_d_pin_cap ? MODE_DP_SRC : 0)); + + pd_port->dp_first_connected = DEFAULT_DP_FIRST_CONNECTED; + pd_port->dp_second_connected = DEFAULT_DP_SECOND_CONNECTED; + + if (of_property_read_string(np, "1st_connection", &connection) == 0) { + pr_info("dp, 1st_connection\n"); + for (i = 0; i < ARRAY_SIZE(dp_connect_mode); i++) { + if (strcasecmp(connection, + dp_connect_mode[i].conn_mode) == 0) { + pd_port->dp_first_connected = + dp_connect_mode[i].val; + break; + } + } + } + + if (of_property_read_string(np, "2nd_connection", &connection) == 0) { + pr_info("dp, 2nd_connection\n"); + for (i = 0; i < ARRAY_SIZE(dp_connect_mode); i++) { + if (strcasecmp(connection, + dp_connect_mode[i].conn_mode) == 0) { + pd_port->dp_second_connected = + dp_connect_mode[i].val; + break; + } + } + } + /* 2nd connection must not be BOTH */ + PD_BUG_ON(pd_port->dp_second_connected == DPSTS_BOTH_CONNECTED); + /* UFP or DFP can't both be invalid */ + PD_BUG_ON(ufp_d_pin_cap == 0 && dfp_d_pin_cap == 0); + if (pd_port->dp_first_connected == DPSTS_BOTH_CONNECTED) { + PD_BUG_ON(ufp_d_pin_cap == 0); + PD_BUG_ON(dfp_d_pin_cap == 0); + } + + return true; +} +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#endif /* CONFIG_USB_PD_ALT_MODE */ +#endif /* CONFIG_USB_POWER_DELIVERY */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_dpm_core.c b/drivers/usb/typec/mmi_tcpc/pd_dpm_core.c new file mode 100644 index 000000000000..72a1d29b7621 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_dpm_core.c @@ -0,0 +1,2342 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * PD Device Policy Manager Core Driver + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" +#include "inc/pd_dpm_core.h" +#include "inc/pd_dpm_pdo_select.h" +#include "inc/pd_core.h" +#include "pd_dpm_prv.h" + +struct pd_device_policy_manager { + uint8_t temp; +}; + +static const struct svdm_svid_ops svdm_svid_ops[] = { +#ifdef CONFIG_USB_PD_ALT_MODE + { + .name = "DisplayPort", + .svid = USB_SID_DISPLAYPORT, + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + .dfp_inform_id = dp_dfp_u_notify_discover_id, + .dfp_inform_svids = dp_dfp_u_notify_discover_svid, + .dfp_inform_modes = dp_dfp_u_notify_discover_modes, + + .dfp_inform_enter_mode = dp_dfp_u_notify_enter_mode, + .dfp_inform_exit_mode = dp_dfp_u_notify_exit_mode, + + .dfp_inform_attention = dp_dfp_u_notify_attention, +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + + .ufp_request_enter_mode = dp_ufp_u_request_enter_mode, + .ufp_request_exit_mode = dp_ufp_u_request_exit_mode, + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + .notify_pe_startup = dp_dfp_u_notify_pe_startup, + .notify_pe_ready = dp_dfp_u_notify_pe_ready, +#endif /* #ifdef CONFIG_USB_PD_ALT_MODE_DFP */ + + .reset_state = dp_reset_state, + .parse_svid_data = dp_parse_svid_data, + }, +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_RICHTEK_UVDM + { + .name = "Richtek", + .svid = USB_VID_RICHTEK, + + .dfp_notify_uvdm = richtek_dfp_notify_uvdm, + .ufp_notify_uvdm = richtek_ufp_notify_uvdm, + + .notify_pe_startup = richtek_dfp_notify_pe_startup, + .notify_pe_ready = richtek_dfp_notify_pe_ready, + }, +#endif /* CONFIG_USB_PD_RICHTEK_UVDM */ + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC + { + .name = "Direct Charge", + .svid = USB_VID_DIRECTCHARGE, + + .dfp_inform_id = dc_dfp_notify_discover_id, + .dfp_inform_svids = dc_dfp_notify_discover_svid, + .dfp_inform_modes = dc_dfp_notify_discover_modes, + + .dfp_inform_enter_mode = dc_dfp_notify_enter_mode, + .dfp_inform_exit_mode = dc_dfp_notify_exit_mode, + + .notify_pe_startup = dc_dfp_notify_pe_startup, + .notify_pe_ready = dc_dfp_notify_pe_ready, + + .dfp_notify_uvdm = dc_dfp_notify_uvdm, + .ufp_notify_uvdm = dc_ufp_notify_uvdm, + + .parse_svid_data = dc_parse_svid_data, + .reset_state = dc_reset_state, + }, +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ +}; + +int dpm_check_supported_modes(void) +{ + int i; + bool is_disorder = false; + bool found_error = false; + + for (i = 0; i < ARRAY_SIZE(svdm_svid_ops); i++) { + if (i < (ARRAY_SIZE(svdm_svid_ops) - 1)) { + if (svdm_svid_ops[i + 1].svid <= + svdm_svid_ops[i].svid) + is_disorder = true; + } + pr_info("SVDM supported mode [%d]: name = %s, svid = 0x%x\n", + i, svdm_svid_ops[i].name, + svdm_svid_ops[i].svid); + } + pr_info("%s : found \"disorder\"...\n", __func__); + found_error |= is_disorder; + return found_error ? -EFAULT : 0; +} + +/* + * DPM Init + */ + +static void pd_dpm_update_pdos_flags(struct pd_port *pd_port, uint32_t pdo) +{ + uint16_t dpm_flags = pd_port->pe_data.dpm_flags + & ~DPM_FLAGS_RESET_PARTNER_MASK; + + /* Only update PDO flags if pdo's type is fixed */ + if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_FIXED) { + if (pdo & PDO_FIXED_DUAL_ROLE) + dpm_flags |= DPM_FLAGS_PARTNER_DR_POWER; + + if (pdo & PDO_FIXED_DATA_SWAP) + dpm_flags |= DPM_FLAGS_PARTNER_DR_DATA; + + if (pdo & PDO_FIXED_EXTERNAL) + dpm_flags |= DPM_FLAGS_PARTNER_EXTPOWER; + + if (pdo & PDO_FIXED_COMM_CAP) + dpm_flags |= DPM_FLAGS_PARTNER_USB_COMM; + + if (pdo & PDO_FIXED_SUSPEND) + dpm_flags |= DPM_FLAGS_PARTNER_USB_SUSPEND; + } + + pd_port->pe_data.dpm_flags = dpm_flags; +} + + +int pd_dpm_send_sink_caps(struct pd_port *pd_port) +{ + struct pd_port_power_caps *snk_cap = &pd_port->local_snk_cap; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (pd_check_rev30(pd_port)) + snk_cap->nr = pd_port->local_snk_cap_nr_pd30; + else + snk_cap->nr = pd_port->local_snk_cap_nr_pd20; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + return pd_send_sop_data_msg(pd_port, PD_DATA_SINK_CAP, + snk_cap->nr, snk_cap->pdos); +} + +int pd_dpm_send_source_caps(struct pd_port *pd_port) +{ + uint8_t i; + uint32_t cable_curr = 3000; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + struct pd_port_power_caps *src_cap0 = &pd_port->local_src_cap_default; + struct pd_port_power_caps *src_cap1 = &pd_port->local_src_cap; + + if (pd_port->pe_data.power_cable_present) { + cable_curr = pd_get_cable_current_limit(pd_port); + DPM_DBG("cable_limit: %dmA\n", cable_curr); + } + + src_cap1->nr = src_cap0->nr; + for (i = 0; i < src_cap0->nr; i++) { + src_cap1->pdos[i] = + pd_reset_pdo_power(tcpc, src_cap0->pdos[i], cable_curr); + } + + return pd_send_sop_data_msg(pd_port, PD_DATA_SOURCE_CAP, + src_cap1->nr, src_cap1->pdos); +} + +void pd_dpm_inform_cable_id(struct pd_port *pd_port, bool src_startup) +{ +#ifdef CONFIG_USB_PD_REV30 + struct pe_data *pe_data = &pd_port->pe_data; +#endif /* CONFIG_USB_PD_REV30 */ + uint32_t *payload = pd_get_msg_vdm_data_payload(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (payload) { + memcpy(pd_port->pe_data.cable_vdos, payload, + pd_get_msg_data_size(pd_port)); + + DPM_DBG("InformCable, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + payload[0], payload[1], payload[2], payload[3]); + + dpm_reaction_clear(pd_port, DPM_REACTION_DISCOVER_CABLE); + } else { +#ifdef CONFIG_USB_PD_REV30 + if (pe_data->discover_id_counter >= PD_DISCOVER_ID30_COUNT) + pd_sync_sop_prime_spec_revision(pd_port, PD_REV20); +#endif /* CONFIG_USB_PD_REV30 */ + } + + if (src_startup) + pd_enable_timer(pd_port, PD_TIMER_SOURCE_START); + else + VDM_STATE_DPM_INFORMED(pd_port); +} + +static bool dpm_response_request(struct pd_port *pd_port, bool accept) +{ + if (accept) + return pd_put_dpm_ack_event(pd_port); + + return pd_put_dpm_nak_event(pd_port, PD_DPM_NAK_REJECT); +} + +/* ---- SNK ---- */ + +static void dpm_build_sink_pdo_info(struct dpm_pdo_info_t *sink_pdo_info, + uint8_t type, int request_v, int request_i) +{ + sink_pdo_info->type = type; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (type == DPM_PDO_TYPE_APDO) { + request_v = (request_v / 20) * 20; + request_i = (request_i / 50) * 50; + } else +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + request_i = (request_i / 10) * 10; + + sink_pdo_info->vmin = sink_pdo_info->vmax = request_v; + sink_pdo_info->ma = request_i; + sink_pdo_info->uw = request_v * request_i; +} + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +static int pps_request_thread_fn(void *data) +{ + struct tcpc_device *tcpc = data; + struct pd_port *pd_port = &tcpc->pd_port; + long ret = 0; + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_REQUEST_AGAIN, + }; + + while (true) { + wait_event(pd_port->pps_request_wait_que, + atomic_read(&pd_port->pps_request) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + do { + ret = wait_event_timeout(pd_port->pps_request_wait_que, + !atomic_read(&pd_port->pps_request) || + kthread_should_stop(), + msecs_to_jiffies(7*1000)); + if (ret) + break; + pd_put_deferred_tcp_event(tcpc, &tcp_event); + } while (true); + } + + return 0; +} + +void pd_dpm_start_pps_request_thread(struct pd_port *pd_port, bool en) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_INFO("pps_thread (%s)\n", en ? "start" : "end"); + if (en) { + __pm_stay_awake(pd_port->pps_request_wake_lock); + atomic_set(&pd_port->pps_request, true); + wake_up(&pd_port->pps_request_wait_que); + } else { + atomic_set(&pd_port->pps_request, false); + wake_up(&pd_port->pps_request_wait_que); + __pm_relax(pd_port->pps_request_wake_lock); + } +} + +static bool dpm_build_request_info_apdo( + struct pd_port *pd_port, struct dpm_rdo_info_t *req_info, + struct pd_port_power_caps *src_cap, uint8_t charging_policy) +{ + struct dpm_pdo_info_t sink_pdo_info; + + dpm_build_sink_pdo_info(&sink_pdo_info, DPM_PDO_TYPE_APDO, + pd_port->request_v_apdo, pd_port->request_i_apdo); + + return dpm_find_match_req_info(req_info, + &sink_pdo_info, src_cap->nr, src_cap->pdos, + -1, charging_policy); +} +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +static bool dpm_build_request_info_pdo( + struct pd_port *pd_port, struct dpm_rdo_info_t *req_info, + struct pd_port_power_caps *src_cap, uint8_t charging_policy) +{ + bool find_cap = false; + int i, max_uw = -1; + struct dpm_pdo_info_t sink_pdo_info; + struct pd_port_power_caps *snk_cap = &pd_port->local_snk_cap; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + for (i = 0; i < snk_cap->nr; i++) { + DPM_DBG("EvaSinkCap%d\n", i+1); + dpm_extract_pdo_info(snk_cap->pdos[i], &sink_pdo_info); + + find_cap = dpm_find_match_req_info(req_info, + &sink_pdo_info, src_cap->nr, src_cap->pdos, + max_uw, charging_policy); + + if (find_cap) { + if (req_info->type == DPM_PDO_TYPE_BAT) + max_uw = req_info->oper_uw; + else + max_uw = req_info->vmax * req_info->oper_ma; + + DPM_DBG("Find SrcCap%d(%s):%d mw\n", + req_info->pos, req_info->mismatch ? + "Mismatch" : "Match", max_uw/1000); + pd_port->pe_data.local_selected_cap = i + 1; + } + } + + return max_uw > 0; +} + +static bool dpm_build_request_info( + struct pd_port *pd_port, struct dpm_rdo_info_t *req_info) +{ + int i; + uint8_t charging_policy = pd_port->dpm_charging_policy; + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + memset(req_info, 0, sizeof(struct dpm_rdo_info_t)); + + DPM_INFO("Policy=0x%X\n", charging_policy); + + for (i = 0; i < src_cap->nr; i++) + DPM_DBG("SrcCap%d: 0x%08x\n", i+1, src_cap->pdos[i]); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if ((charging_policy & DPM_CHARGING_POLICY_MASK) + == DPM_CHARGING_POLICY_PPS) { + return dpm_build_request_info_apdo( + pd_port, req_info, src_cap, charging_policy); + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + return dpm_build_request_info_pdo( + pd_port, req_info, src_cap, charging_policy); +} + +static bool dpm_build_default_request_info( + struct pd_port *pd_port, struct dpm_rdo_info_t *req_info) +{ + struct dpm_pdo_info_t sink, source; + struct pd_port_power_caps *snk_cap = &pd_port->local_snk_cap; + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + + pd_port->pe_data.local_selected_cap = 1; + + dpm_extract_pdo_info(snk_cap->pdos[0], &sink); + dpm_extract_pdo_info(src_cap->pdos[0], &source); + + req_info->pos = 1; + req_info->type = source.type; + req_info->mismatch = true; + req_info->vmax = 5000; + req_info->vmin = 5000; + + if (req_info->type == DPM_PDO_TYPE_BAT) { + req_info->max_uw = sink.uw; + req_info->oper_uw = source.uw; + + } else { + req_info->max_ma = sink.ma; + req_info->oper_ma = source.ma; + } + + return true; +} + +static inline void dpm_update_request_i_new( + struct pd_port *pd_port, struct dpm_rdo_info_t *req_info) +{ + if (req_info->mismatch) + pd_port->request_i_new = pd_port->request_i_op; + else + pd_port->request_i_new = pd_port->request_i_max; +} + +static inline void dpm_update_request_bat(struct pd_port *pd_port, + struct dpm_rdo_info_t *req_info, uint32_t flags) +{ + uint32_t mw_op, mw_max; + + mw_op = req_info->oper_uw / 1000; + mw_max = req_info->max_uw / 1000; + + pd_port->request_i_op = req_info->oper_uw / req_info->vmin; + pd_port->request_i_max = req_info->max_uw / req_info->vmin; + + dpm_update_request_i_new(pd_port, req_info); + + pd_port->last_rdo = RDO_BATT( + req_info->pos, mw_op, mw_max, flags); +} + +static inline void dpm_update_request_not_bat(struct pd_port *pd_port, + struct dpm_rdo_info_t *req_info, uint32_t flags) +{ + pd_port->request_i_op = req_info->oper_ma; + pd_port->request_i_max = req_info->max_ma; + + dpm_update_request_i_new(pd_port, req_info); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (req_info->type == DPM_PDO_TYPE_APDO) { + pd_port->request_apdo_new = true; + pd_port->last_rdo = RDO_APDO( + req_info->pos, req_info->vmin, + req_info->oper_ma, flags); + return; + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + if (req_info->mismatch && (pd_port->cap_miss_match == 0x3)) { + pd_port->cap_miss_match = 0; + req_info->mismatch = 0; + flags &= ~RDO_CAP_MISMATCH; + pd_port->last_rdo = RDO_FIXED( + req_info->pos, req_info->oper_ma, + req_info->oper_ma, flags); + } else { + pd_port->last_rdo = RDO_FIXED( + req_info->pos, req_info->oper_ma, + req_info->max_ma, flags); + } +} + +static inline void dpm_update_request( + struct pd_port *pd_port, struct dpm_rdo_info_t *req_info) +{ + uint32_t flags = 0; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + pd_port->request_apdo_new = false; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + if (pd_port->dpm_caps & DPM_CAP_LOCAL_GIVE_BACK) + flags |= RDO_GIVE_BACK; + + if (pd_port->dpm_caps & DPM_CAP_LOCAL_NO_SUSPEND) + flags |= RDO_NO_SUSPEND; + + if (pd_port->dpm_caps & DPM_CAP_LOCAL_USB_COMM) + flags |= RDO_COMM_CAP; + + if (req_info->mismatch) { + flags |= RDO_CAP_MISMATCH; + pd_port->cap_miss_match |= 0x1; + DPM_INFO("cap miss match case\n"); + } + + pd_port->request_v_new = req_info->vmax; + + if (req_info->type == DPM_PDO_TYPE_BAT) + dpm_update_request_bat(pd_port, req_info, flags); + else + dpm_update_request_not_bat(pd_port, req_info, flags); + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE + pd_notify_pe_direct_charge(pd_port, + req_info->vmin < TCPC_VBUS_SINK_5V); +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ +} + +int pd_dpm_update_tcp_request(struct pd_port *pd_port, + struct tcp_dpm_pd_request *pd_req) +{ + bool find_cap = false; + uint8_t type = DPM_PDO_TYPE_FIXED; + struct dpm_rdo_info_t req_info; + struct dpm_pdo_info_t sink_pdo_info; + uint8_t charging_policy = pd_port->dpm_charging_policy; + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + memset(&req_info, 0, sizeof(struct dpm_rdo_info_t)); + + DPM_DBG("charging_policy=0x%X\n", charging_policy); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if ((charging_policy & DPM_CHARGING_POLICY_MASK) + == DPM_CHARGING_POLICY_PPS) + type = DPM_PDO_TYPE_APDO; +#endif /*CONFIG_USB_PD_REV30_PPS_SINK */ + + dpm_build_sink_pdo_info(&sink_pdo_info, type, pd_req->mv, pd_req->ma); + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (pd_port->request_apdo && + (sink_pdo_info.vmin == pd_port->request_v) && + (sink_pdo_info.ma == pd_port->request_i)) + return TCP_DPM_RET_DENIED_REPEAT_REQUEST; +#endif /*CONFIG_USB_PD_REV30_PPS_SINK */ + + find_cap = dpm_find_match_req_info(&req_info, + &sink_pdo_info, src_cap->nr, src_cap->pdos, + -1, charging_policy); + + if (!find_cap) { + DPM_INFO("Can't find match_cap\n"); + return TCP_DPM_RET_DENIED_INVALID_REQUEST; + } + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if ((charging_policy & DPM_CHARGING_POLICY_MASK) + == DPM_CHARGING_POLICY_PPS) { + pd_port->request_v_apdo = sink_pdo_info.vmin; + pd_port->request_i_apdo = sink_pdo_info.ma; + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + dpm_update_request(pd_port, &req_info); + return TCP_DPM_RET_SUCCESS; +} + +int pd_dpm_update_tcp_request_ex(struct pd_port *pd_port, + struct tcp_dpm_pd_request_ex *pd_req) +{ + struct dpm_pdo_info_t source; + struct dpm_rdo_info_t req_info; + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_req->pos > src_cap->nr) + return false; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (pd_port->dpm_charging_policy == DPM_CHARGING_POLICY_PPS) { + DPM_INFO("Reject tcp_rqeuest_ex if charging_policy=pps\n"); + return TCP_DPM_RET_DENIED_INVALID_REQUEST; + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + dpm_extract_pdo_info(src_cap->pdos[pd_req->pos-1], &source); + + req_info.pos = pd_req->pos; + req_info.type = source.type; + req_info.mismatch = false; + req_info.vmax = source.vmax; + req_info.vmin = source.vmin; + + if (req_info.type == DPM_PDO_TYPE_BAT) { + req_info.max_uw = pd_req->max_uw; + req_info.oper_uw = pd_req->oper_uw; + if (pd_req->oper_uw < pd_req->max_uw) + req_info.mismatch = true; + } else { + req_info.max_ma = pd_req->max_ma; + req_info.oper_ma = pd_req->oper_ma; + if (pd_req->oper_ma < pd_req->max_ma) + req_info.mismatch = true; + } + + dpm_update_request(pd_port, &req_info); + return TCP_DPM_RET_SUCCESS; +} + +int pd_dpm_update_tcp_request_again(struct pd_port *pd_port) +{ + bool find_cap = false; + int sink_nr, source_nr; + struct dpm_rdo_info_t req_info; + struct pd_port_power_caps *snk_cap = &pd_port->local_snk_cap; + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + sink_nr = snk_cap->nr; + source_nr = src_cap->nr; + + if ((source_nr <= 0) || (sink_nr <= 0)) { + DPM_INFO("SrcNR or SnkNR = 0\n"); + return TCP_DPM_RET_DENIED_INVALID_REQUEST; + } + + find_cap = dpm_build_request_info(pd_port, &req_info); + + /* If we can't find any cap to use, choose default setting */ + if (!find_cap) { + DPM_INFO("Can't find any SrcCap\n"); + dpm_build_default_request_info(pd_port, &req_info); + } else + DPM_INFO("Select SrcCap%d\n", req_info.pos); + + dpm_update_request(pd_port, &req_info); + return TCP_DPM_RET_SUCCESS; +} + +void pd_dpm_snk_evaluate_caps(struct pd_port *pd_port) +{ + bool find_cap = false; + struct dpm_rdo_info_t req_info; + struct pd_port_power_caps *snk_cap = &pd_port->local_snk_cap; + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PD_BUG_ON(pd_get_msg_data_payload(pd_port) == NULL); + + pd_dpm_dr_inform_source_cap(pd_port); + + if ((src_cap->nr <= 0) || (snk_cap->nr <= 0)) { + DPM_INFO("SrcNR or SnkNR = 0\n"); + return; + } + + find_cap = dpm_build_request_info(pd_port, &req_info); + + /* If we can't find any cap to use, choose default setting */ + if (!find_cap) { + DPM_INFO("Can't find any SrcCap\n"); + dpm_build_default_request_info(pd_port, &req_info); + } else + DPM_INFO("Select SrcCap%d\n", req_info.pos); + + dpm_update_request(pd_port, &req_info); + + if (req_info.pos > 0) + pd_put_dpm_notify_event(pd_port, req_info.pos); +} + +void pd_dpm_snk_standby_power(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_SNK_STANDBY_POWER + /* + * pSnkStdby : + * Maximum power consumption while in Sink Standby. (2.5W) + * I1 = (pSnkStdby/VBUS) + * I2 = (pSnkStdby/VBUS) + cSnkBulkPd(DVBUS/Dt) + * STANDBY_UP = I1 < I2, STANDBY_DOWN = I1 > I2 + * + * tSnkNewPower (t1): + * Maximum transition time between power levels. (15ms) + */ + + uint8_t type; + int ma = -1; + int standby_curr = 2500000 / max(pd_port->request_v, + pd_port->request_v_new); + +#ifdef CONFIG_USB_PD_VCONN_SAFE5V_ONLY + struct tcpc_device *tcpc = pd_port->tcpc; + struct pe_data *pe_data = &pd_port->pe_data; + bool vconn_highv_prot = pd_port->request_v_new > 5000; + + if (!pe_data->vconn_highv_prot && vconn_highv_prot && + tcpc->tcpc_flags & TCPC_FLAGS_VCONN_SAFE5V_ONLY) { + PE_INFO("VC_HIGHV_PROT: %d\n", vconn_highv_prot); + pe_data->vconn_highv_prot_role = pd_port->vconn_role; + pd_set_vconn(pd_port, PD_ROLE_VCONN_OFF); + pe_data->vconn_highv_prot = vconn_highv_prot; + } +#endif /* CONFIG_USB_PD_VCONN_SAFE5V_ONLY */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + /* + * A Sink is not required to transition to Sink Standby + * when operating with a Programmable Power Supply + * (Check it later, Aginst new spec) + */ + if (pd_port->request_apdo_new) + return; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + if (pd_port->request_v_new > pd_port->request_v) { + /* Case2 Increasing the Voltage */ + /* Case3 Increasing the Voltage and Current */ + /* Case4 Increasing the Voltage and Decreasing the Curren */ + ma = standby_curr; + type = TCP_VBUS_CTRL_STANDBY_UP; + } else if (pd_port->request_v_new < pd_port->request_v) { + /* Case5 Decreasing the Voltage and Increasing the Current */ + /* Case7 Decreasing the Voltage */ + /* Case8 Decreasing the Voltage and the Current*/ + ma = standby_curr; + type = TCP_VBUS_CTRL_STANDBY_DOWN; + } else if (pd_port->request_i_new < pd_port->request_i) { + /* Case6 Decreasing the Current, t1 i = new */ + ma = pd_port->request_i_new; + type = TCP_VBUS_CTRL_STANDBY; + } + + if (ma >= 0) { + tcpci_sink_vbus( + pd_port->tcpc, type, pd_port->request_v_new, ma); + } +#else +#ifdef CONFIG_USB_PD_SNK_GOTOMIN + tcpci_sink_vbus(pd_port->tcpc, TCP_VBUS_CTRL_REQUEST, + pd_port->request_v, pd_port->request_i_new); +#endif /* CONFIG_USB_PD_SNK_GOTOMIN */ +#endif /* CONFIG_USB_PD_SNK_STANDBY_POWER */ +} + +void pd_dpm_snk_transition_power(struct pd_port *pd_port) +{ + tcpci_sink_vbus(pd_port->tcpc, TCP_VBUS_CTRL_REQUEST, + pd_port->request_v_new, pd_port->request_i_new); + + pd_port->request_v = pd_port->request_v_new; + pd_port->request_i = pd_port->request_i_new; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (pd_port->request_apdo != pd_port->request_apdo_new) { + pd_port->request_apdo = pd_port->request_apdo_new; + pd_dpm_start_pps_request_thread( + pd_port, pd_port->request_apdo_new); + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +} + +void pd_dpm_snk_hard_reset(struct pd_port *pd_port) +{ + /* + * tSnkHardResetPrepare : + * Time allotted for the Sink power electronics + * to prepare for a Hard Reset + */ + + int mv = 0, ma = 0; + bool ignore_hreset = false; + +#ifdef CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW + if (!pd_port->pe_data.pd_prev_connected) { +#ifdef CONFIG_USB_PD_SNK_IGNORE_HRESET_IF_TYPEC_ONLY + ignore_hreset = true; +#else + ma = -1; + mv = TCPC_VBUS_SINK_5V; +#endif /* CONFIG_USB_PD_SNK_IGNORE_HRESET_IF_TYPEC_ONLY */ + } +#endif /* CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW */ + + if (!ignore_hreset) { + tcpci_sink_vbus( + pd_port->tcpc, TCP_VBUS_CTRL_HRESET, mv, ma); + } + + pd_put_pe_event(pd_port, PD_PE_POWER_ROLE_AT_DEFAULT); +} + +/* ---- SRC ---- */ + +static inline bool dpm_evaluate_request( + struct pd_port *pd_port, uint32_t rdo, uint8_t rdo_pos) +{ + uint32_t pdo; + uint32_t sink_v; + uint32_t op_curr, max_curr; + struct dpm_pdo_info_t src_info; + struct pd_port_power_caps *src_cap = &pd_port->local_src_cap; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + pd_port->pe_data.dpm_flags &= (~DPM_FLAGS_PARTNER_MISMATCH); + + if ((rdo_pos == 0) || (rdo_pos > src_cap->nr)) { + DPM_INFO("RequestPos Wrong (%d)\n", rdo_pos); + return false; + } + + pdo = src_cap->pdos[rdo_pos-1]; + + dpm_extract_pdo_info(pdo, &src_info); + pd_extract_rdo_power(rdo, pdo, &op_curr, &max_curr); + + if (src_info.ma < op_curr) { + DPM_INFO("src_i (%d) < op_i (%d)\n", src_info.ma, op_curr); + return false; + } + + if (rdo & RDO_CAP_MISMATCH) { + /* TODO: handle it later */ + DPM_INFO("CAP_MISMATCH\n"); + pd_port->pe_data.dpm_flags |= DPM_FLAGS_PARTNER_MISMATCH; + } else if (src_info.ma < max_curr) { + DPM_INFO("src_i (%d) < max_i (%d)\n", src_info.ma, max_curr); + return false; + } + + sink_v = src_info.vmin; + +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_APDO) { + sink_v = RDO_APDO_EXTRACT_OP_MV(rdo); + + if ((sink_v < src_info.vmin) || (sink_v > src_info.vmax)) { + DPM_INFO("sink_v (%d) not in src_v (%d~%d)\n", + sink_v, src_info.vmin, src_info.vmax); + return false; + } + } +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ + + /* Accept request */ + + pd_port->request_i_op = op_curr; + pd_port->request_i_max = max_curr; + + if (rdo & RDO_CAP_MISMATCH) + pd_port->request_i_new = op_curr; + else + pd_port->request_i_new = max_curr; + + pd_port->request_v_new = sink_v; + return true; +} + +void pd_dpm_src_evaluate_request(struct pd_port *pd_port) +{ + uint32_t rdo; + uint8_t rdo_pos; + struct pe_data *pe_data; + uint32_t *payload = pd_get_msg_data_payload(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PD_BUG_ON(payload == NULL); + + rdo = payload[0]; + rdo_pos = RDO_POS(rdo); + + DPM_INFO("RequestCap%d\n", rdo_pos); + + pe_data = &pd_port->pe_data; + + if (dpm_evaluate_request(pd_port, rdo, rdo_pos)) { + pe_data->local_selected_cap = rdo_pos; + pd_put_dpm_notify_event(pd_port, rdo_pos); + } else { + /* + * "Contract Invalid" means that the previously + * negotiated Voltage and Current values + * are no longer included in the Sources new Capabilities. + * If the Sink fails to make a valid Request in this case + * then Power Delivery operation is no longer possible + * and Power Delivery mode is exited with a Hard Reset. + */ + + pe_data->invalid_contract = false; + pe_data->local_selected_cap = 0; + pd_put_dpm_nak_event(pd_port, PD_DPM_NAK_REJECT); + } +} + +void pd_dpm_src_transition_power(struct pd_port *pd_port) +{ + pd_enable_vbus_stable_detection(pd_port); + +#ifdef CONFIG_USB_PD_SRC_HIGHCAP_POWER + if (pd_port->request_v > pd_port->request_v_new) { + mutex_lock(&pd_port->tcpc->access_lock); + tcpci_enable_force_discharge( + pd_port->tcpc, true, pd_port->request_v_new); + mutex_unlock(&pd_port->tcpc->access_lock); + } +#endif /* CONFIG_USB_PD_SRC_HIGHCAP_POWER */ + + tcpci_source_vbus(pd_port->tcpc, TCP_VBUS_CTRL_REQUEST, + pd_port->request_v_new, pd_port->request_i_new); + + if (pd_port->request_v == pd_port->request_v_new) + pd_put_vbus_stable_event(pd_port->tcpc); +#if CONFIG_USB_PD_VBUS_STABLE_TOUT + else + pd_enable_timer(pd_port, PD_TIMER_VBUS_STABLE); +#endif /* CONFIG_USB_PD_VBUS_STABLE_TOUT */ + + pd_port->request_v = pd_port->request_v_new; + pd_port->request_i = pd_port->request_i_new; +} + +void pd_dpm_src_hard_reset(struct pd_port *pd_port) +{ + tcpci_source_vbus(pd_port->tcpc, + TCP_VBUS_CTRL_HRESET, TCPC_VBUS_SOURCE_0V, 0); + pd_enable_vbus_safe0v_detection(pd_port); +} + +/* ---- UFP : update_svid_data ---- */ + +static inline bool dpm_ufp_update_svid_data_enter_mode( + struct pd_port *pd_port, uint16_t svid, uint8_t ops) +{ + struct svdm_svid_data *svid_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_DBG("EnterMode (svid0x%04x, ops:%d)\n", svid, ops); + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + + if (svid_data == NULL) + return false; + + /* Only accept 1 mode active at the same time */ + if (svid_data->active_mode) + return false; + + if ((ops == 0) || (ops > svid_data->local_mode.mode_cnt)) + return false; + + svid_data->active_mode = ops; + pd_port->pe_data.modal_operation = true; + + svdm_ufp_request_enter_mode(pd_port, svid, ops); + + tcpci_enter_mode(tcpc, svid, ops, svid_data->local_mode.mode_vdo[ops]); + return true; +} + +static inline bool dpm_ufp_update_svid_data_exit_mode( + struct pd_port *pd_port, uint16_t svid, uint8_t ops) +{ + uint8_t i; + bool modal_operation; + struct svdm_svid_data *svid_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_DBG("ExitMode (svid0x%04x, mode:%d)\n", svid, ops); + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + + if (svid_data == NULL) + return false; + + if (svid_data->active_mode == 0) + return false; + + if ((ops == 0) || (ops == svid_data->active_mode)) { + svid_data->active_mode = 0; + + modal_operation = false; + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + + if (svid_data->active_mode) { + modal_operation = true; + break; + } + } + + pd_port->pe_data.modal_operation = modal_operation; + + svdm_ufp_request_exit_mode(pd_port, svid, ops); + tcpci_exit_mode(pd_port->tcpc, svid); + return true; + } + + return false; +} + + +/* ---- UFP : Response VDM Request ---- */ + +static int dpm_vdm_ufp_response_id(struct pd_port *pd_port) +{ + if (pd_check_rev30(pd_port)) + pd_port->id_vdos[0] = pd_port->id_header; + else + pd_port->id_vdos[0] = VDO_IDH_PD20(pd_port->id_header); + + return pd_reply_svdm_request(pd_port, CMDT_RSP_ACK, + pd_check_rev30(pd_port) ? pd_port->id_vdo_nr : 3, + pd_port->id_vdos); +} + +static int dpm_ufp_response_svids(struct pd_port *pd_port) +{ + struct svdm_svid_data *svid_data; + uint16_t svid_list[2]; + uint32_t svids[VDO_MAX_NR]; + uint8_t i = 0, j = 0, cnt = pd_port->svid_data_cnt; + + PD_BUG_ON(pd_port->svid_data_cnt >= VDO_MAX_SVID_NR); + + while (i < cnt) { + svid_data = &pd_port->svid_data[i++]; + svid_list[0] = svid_data->svid; + + if (i < cnt) { + svid_data = &pd_port->svid_data[i++]; + svid_list[1] = svid_data->svid; + } else + svid_list[1] = 0; + + svids[j++] = VDO_SVID(svid_list[0], svid_list[1]); + } + + if ((cnt % 2) == 0) + svids[j++] = VDO_SVID(0, 0); + + return pd_reply_svdm_request(pd_port, CMDT_RSP_ACK, j, svids); +} + +static int dpm_vdm_ufp_response_modes(struct pd_port *pd_port) +{ + struct svdm_svid_data *svid_data; + uint16_t svid = dpm_vdm_get_svid(pd_port); + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + + PD_BUG_ON(svid_data == NULL); + + return pd_reply_svdm_request( + pd_port, CMDT_RSP_ACK, + svid_data->local_mode.mode_cnt, + svid_data->local_mode.mode_vdo); +} + +/* ---- UFP : Handle VDM Request ---- */ + +void pd_dpm_ufp_request_id_info(struct pd_port *pd_port) +{ + bool ack = dpm_vdm_get_svid(pd_port) == USB_SID_PD; + + if (!ack) { + dpm_vdm_reply_svdm_nak(pd_port); + return; + } + + dpm_vdm_ufp_response_id(pd_port); +} + +void pd_dpm_ufp_request_svid_info(struct pd_port *pd_port) +{ + bool ack = false; + + if (pd_is_support_modal_operation(pd_port)) + ack = (dpm_vdm_get_svid(pd_port) == USB_SID_PD); + + if (!ack) { + dpm_vdm_reply_svdm_nak(pd_port); + return; + } + + dpm_ufp_response_svids(pd_port); +} + +void pd_dpm_ufp_request_mode_info(struct pd_port *pd_port) +{ + uint16_t svid = dpm_vdm_get_svid(pd_port); + bool ack = dpm_get_svdm_svid_data(pd_port, svid) != NULL; + + if (!ack) { + dpm_vdm_reply_svdm_nak(pd_port); + return; + } + + dpm_vdm_ufp_response_modes(pd_port); +} + +void pd_dpm_ufp_request_enter_mode(struct pd_port *pd_port) +{ + bool ack = dpm_ufp_update_svid_data_enter_mode(pd_port, + dpm_vdm_get_svid(pd_port), dpm_vdm_get_ops(pd_port)); + + dpm_vdm_reply_svdm_request(pd_port, ack); +} + +void pd_dpm_ufp_request_exit_mode(struct pd_port *pd_port) +{ + bool ack = dpm_ufp_update_svid_data_exit_mode(pd_port, + dpm_vdm_get_svid(pd_port), dpm_vdm_get_ops(pd_port)); + + dpm_vdm_reply_svdm_request(pd_port, ack); +} + +/* ---- DFP : update_svid_data ---- */ + +static inline void dpm_dfp_update_partner_id( + struct pd_port *pd_port, uint32_t *payload) +{ +#ifdef CONFIG_USB_PD_KEEP_PARTNER_ID + uint8_t cnt = pd_get_msg_vdm_data_count(pd_port); + uint32_t size = sizeof(uint32_t) * (cnt); + + pd_port->pe_data.partner_id_present = true; + memcpy(pd_port->pe_data.partner_vdos, payload, size); +#endif /* CONFIG_USB_PD_KEEP_PARTNER_ID */ +} +static inline void dpm_dfp_update_svid_data_exist( + struct pd_port *pd_port, uint16_t svid) +{ + uint8_t k; + struct svdm_svid_data *svid_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; +#ifdef CONFIG_USB_PD_KEEP_SVIDS + struct svdm_svid_list *list = &pd_port->pe_data.remote_svid_list; + + if (list->cnt < VDO_MAX_SVID_NR) + list->svids[list->cnt++] = svid; + else + DPM_INFO("ERR:SVIDCNT\n"); +#endif /* CONFIG_USB_PD_KEEP_SVIDS */ + + for (k = 0; k < pd_port->svid_data_cnt; k++) { + + svid_data = &pd_port->svid_data[k]; + + if (svid_data->svid == svid) + svid_data->exist = 1; + } +} + +static inline void dpm_dfp_update_svid_data_modes(struct pd_port *pd_port, + uint16_t svid, uint32_t *mode_list, uint8_t count) +{ + uint8_t i; + struct svdm_svid_data *svid_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_DBG("InformMode (0x%04x:%d):\n", svid, count); + for (i = 0; i < count; i++) + DPM_DBG("Mode[%d]: 0x%08x\n", i, mode_list[i]); + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return; + + svid_data->remote_mode.mode_cnt = count; + + if (count != 0) { + memcpy(svid_data->remote_mode.mode_vdo, + mode_list, sizeof(uint32_t) * count); + } +} + +static inline void dpm_dfp_update_svid_enter_mode( + struct pd_port *pd_port, uint16_t svid, uint8_t ops) +{ + struct svdm_svid_data *svid_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_DBG("EnterMode (svid0x%04x, mode:%d)\n", svid, ops); + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return; + + svid_data->active_mode = ops; + pd_port->pe_data.modal_operation = true; + + tcpci_enter_mode(tcpc, + svid_data->svid, ops, svid_data->remote_mode.mode_vdo[ops]); +} + +static inline void dpm_dfp_update_svid_data_exit_mode( + struct pd_port *pd_port, uint16_t svid, uint8_t ops) +{ + uint8_t i; + bool modal_operation; + struct svdm_svid_data *svid_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_DBG("ExitMode (svid0x%04x, mode:%d)\n", svid, ops); + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return; + + if ((ops == 7) || (ops == svid_data->active_mode)) { + svid_data->active_mode = 0; + + modal_operation = false; + for (i = 0; i < pd_port->svid_data_cnt; i++) { + + svid_data = &pd_port->svid_data[i]; + + if (svid_data->active_mode) { + modal_operation = true; + break; + } + } + + pd_port->pe_data.modal_operation = modal_operation; + tcpci_exit_mode(tcpc, svid); + } +} + + +/* ---- DFP : Inform VDM Result ---- */ + +void pd_dpm_dfp_inform_id(struct pd_port *pd_port, bool ack) +{ + uint32_t *payload = pd_get_msg_vdm_data_payload(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + VDM_STATE_DPM_INFORMED(pd_port); + + if (!payload) { + dpm_reaction_clear(pd_port, DPM_REACTION_DISCOVER_ID | + DPM_REACTION_DISCOVER_SVID); + return; + } + + if (ack) { + DPM_DBG("InformID, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + payload[0], payload[1], payload[2], payload[3]); + + dpm_dfp_update_partner_id(pd_port, payload); + } + + if (!pd_port->pe_data.vdm_discard_retry_flag) { + /* + * For PD compliance test, + * If device doesn't reply discoverID + * or doesn't support modal operation, + * then don't send discoverSVID + */ + if (!ack || !(payload[0] & PD_IDH_MODAL_SUPPORT)) + dpm_reaction_clear(pd_port, DPM_REACTION_DISCOVER_SVID); + else + dpm_reaction_set(pd_port, DPM_REACTION_DISCOVER_SVID); + + svdm_dfp_inform_id(pd_port, ack); + dpm_reaction_clear(pd_port, DPM_REACTION_DISCOVER_ID); + } +} + +static inline int dpm_dfp_consume_svids( + struct pd_port *pd_port, uint32_t *svid_list, uint8_t count) +{ + bool discover_again = true; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + uint8_t i, j; + uint16_t svid[2]; + + DPM_DBG("InformSVID (%d):\n", count); + + if (count < 6) + discover_again = false; + + for (i = 0; i < count; i++) { + svid[0] = PD_VDO_SVID_SVID0(svid_list[i]); + svid[1] = PD_VDO_SVID_SVID1(svid_list[i]); + + DPM_DBG("svid[%d]: 0x%04x 0x%04x\n", i, svid[0], svid[1]); + + for (j = 0; j < 2; j++) { + if (svid[j] == 0) { + discover_again = false; + break; + } + + dpm_dfp_update_svid_data_exist(pd_port, svid[j]); + } + } + + if (discover_again) { + DPM_DBG("DiscoverSVID Again\n"); + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_DISCOVER_SVIDS); + return 1; + } + + return 0; +} + +void pd_dpm_dfp_inform_svids(struct pd_port *pd_port, bool ack) +{ + uint8_t count; + uint32_t *svid_list; + + VDM_STATE_DPM_INFORMED(pd_port); + + if (ack) { + count = pd_get_msg_vdm_data_count(pd_port); + svid_list = pd_get_msg_vdm_data_payload(pd_port); + if (!svid_list) + return; + if (dpm_dfp_consume_svids(pd_port, svid_list, count)) + return; + } + + if (!pd_port->pe_data.vdm_discard_retry_flag) { + svdm_dfp_inform_svids(pd_port, ack); + dpm_reaction_clear(pd_port, DPM_REACTION_DISCOVER_SVID); + } +} + +void pd_dpm_dfp_inform_modes(struct pd_port *pd_port, bool ack) +{ + uint8_t count; + uint16_t svid = 0; + uint32_t *payload; + uint16_t expected_svid = pd_port->mode_svid; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (ack) { + svid = dpm_vdm_get_svid(pd_port); + + if (svid != expected_svid) { + ack = false; + DPM_INFO("Not expected SVID (0x%04x, 0x%04x)\n", + svid, expected_svid); + } else { + count = pd_get_msg_vdm_data_count(pd_port); + payload = pd_get_msg_vdm_data_payload(pd_port); + if (payload) + dpm_dfp_update_svid_data_modes( + pd_port, svid, payload, count); + } + } + + svdm_dfp_inform_modes(pd_port, expected_svid, ack); + VDM_STATE_DPM_INFORMED(pd_port); +} + +void pd_dpm_dfp_inform_enter_mode(struct pd_port *pd_port, bool ack) +{ + uint8_t ops = 0; + uint16_t svid = 0; + uint16_t expected_svid = pd_port->mode_svid; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (ack) { + ops = dpm_vdm_get_ops(pd_port); + svid = dpm_vdm_get_svid(pd_port); + + /* TODO: check ops later ?! */ + if (svid != expected_svid) { + ack = false; + DPM_INFO("Not expected SVID (0x%04x, 0x%04x)\n", + svid, expected_svid); + } else { + dpm_dfp_update_svid_enter_mode(pd_port, svid, ops); + } + } + + svdm_dfp_inform_enter_mode(pd_port, expected_svid, ops, ack); + VDM_STATE_DPM_INFORMED(pd_port); +} + +void pd_dpm_dfp_inform_exit_mode(struct pd_port *pd_port) +{ + uint8_t ops = dpm_vdm_get_ops(pd_port); + uint16_t svid = dpm_vdm_get_svid(pd_port); + uint8_t expected_ops = pd_port->mode_obj_pos; + uint16_t expected_svid = pd_port->mode_svid; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if ((expected_svid != svid) || (expected_ops != ops)) + DPM_DBG("expected_svid & ops wrong\n"); + + dpm_dfp_update_svid_data_exit_mode( + pd_port, expected_svid, expected_ops); + + svdm_dfp_inform_exit_mode(pd_port, expected_svid, expected_ops); + VDM_STATE_DPM_INFORMED(pd_port); +} + +void pd_dpm_dfp_inform_attention(struct pd_port *pd_port) +{ +#if DPM_DBG_ENABLE + uint8_t ops = dpm_vdm_get_ops(pd_port); +#endif + uint16_t svid = dpm_vdm_get_svid(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_DBG("Attention (svid0x%04x, mode:%d)\n", svid, ops); + + svdm_dfp_inform_attention(pd_port, svid); + VDM_STATE_DPM_INFORMED(pd_port); +} + +/* ---- Unstructured VDM ---- */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + +void pd_dpm_ufp_recv_uvdm(struct pd_port *pd_port) +{ + struct svdm_svid_data *svid_data; + uint16_t svid = dpm_vdm_get_svid(pd_port); + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + + pd_port->uvdm_svid = svid; + pd_port->uvdm_cnt = pd_get_msg_data_count(pd_port); + + memcpy(pd_port->uvdm_data, + pd_get_msg_data_payload(pd_port), + pd_get_msg_data_size(pd_port)); + + if (svid_data) { + if (svid_data->ops->ufp_notify_uvdm) + svid_data->ops->ufp_notify_uvdm(pd_port, svid_data); + else + VDM_STATE_DPM_INFORMED(pd_port); + + tcpci_notify_uvdm(pd_port->tcpc, true); + } else { + pd_put_dpm_event(pd_port, PD_DPM_NOT_SUPPORT); + VDM_STATE_DPM_INFORMED(pd_port); + } +} + +void pd_dpm_dfp_send_uvdm(struct pd_port *pd_port) +{ + pd_send_custom_vdm(pd_port, TCPC_TX_SOP); + pd_port->uvdm_svid = PD_VDO_VID(pd_port->uvdm_data[0]); + + if (pd_port->uvdm_wait_resp) + VDM_STATE_RESPONSE_CMD(pd_port, PD_TIMER_UVDM_RESPONSE); +} + +void pd_dpm_dfp_inform_uvdm(struct pd_port *pd_port, bool ack) +{ + uint16_t svid; + uint16_t expected_svid = pd_port->uvdm_svid; + struct svdm_svid_data *svid_data = + dpm_get_svdm_svid_data(pd_port, expected_svid); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (ack && pd_port->uvdm_wait_resp) { + svid = dpm_vdm_get_svid(pd_port); + + if (svid != expected_svid) { + ack = false; + DPM_INFO("Not expected SVID (0x%04x, 0x%04x)\n", + svid, expected_svid); + } else { + pd_port->uvdm_cnt = pd_get_msg_data_count(pd_port); + memcpy(pd_port->uvdm_data, + pd_get_msg_data_payload(pd_port), + pd_get_msg_data_size(pd_port)); + } + } + + if (svid_data) { + if (svid_data->ops->dfp_notify_uvdm) + svid_data->ops->dfp_notify_uvdm( + pd_port, svid_data, ack); + } + + tcpci_notify_uvdm(tcpc, ack); + pd_notify_tcp_vdm_event_2nd_result(pd_port, + ack ? TCP_DPM_RET_VDM_ACK : TCP_DPM_RET_VDM_NAK); + VDM_STATE_DPM_INFORMED(pd_port); +} + +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + + +/* + * DRP : Inform Source/Sink Cap + */ + +void pd_dpm_dr_inform_sink_cap(struct pd_port *pd_port) +{ + const uint32_t reaction_clear = DPM_REACTION_GET_SINK_CAP + | DPM_REACTION_ATTEMPT_GET_FLAG; + + struct pd_event *pd_event = pd_get_curr_pd_event(pd_port); + struct pd_port_power_caps *snk_cap = &pd_port->pe_data.remote_snk_cap; + + if (!pd_event_data_msg_match(pd_event, PD_DATA_SINK_CAP)) + return; + + snk_cap->nr = pd_get_msg_data_count(pd_port); + memcpy(snk_cap->pdos, + pd_get_msg_data_payload(pd_port), + pd_get_msg_data_size(pd_port)); + + pd_dpm_update_pdos_flags(pd_port, snk_cap->pdos[0]); + + dpm_reaction_clear(pd_port, reaction_clear); +} + +void pd_dpm_dr_inform_source_cap(struct pd_port *pd_port) +{ + uint32_t reaction_clear = DPM_REACTION_GET_SOURCE_CAP + | DPM_REACTION_ATTEMPT_GET_FLAG; + + struct pd_event *pd_event = pd_get_curr_pd_event(pd_port); + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + + if (!pd_event_data_msg_match(pd_event, PD_DATA_SOURCE_CAP)) + return; + + src_cap->nr = pd_get_msg_data_count(pd_port); + memcpy(src_cap->pdos, + pd_get_msg_data_payload(pd_port), + pd_get_msg_data_size(pd_port)); + + pd_dpm_update_pdos_flags(pd_port, src_cap->pdos[0]); + + if (!(pd_port->pe_data.dpm_flags & DPM_FLAGS_PARTNER_DR_POWER)) + reaction_clear |= DPM_REACTION_GET_SINK_CAP; + + dpm_reaction_clear(pd_port, reaction_clear); +} + +/* + * DRP : Data Role Swap + */ + +#ifdef CONFIG_USB_PD_DR_SWAP + +void pd_dpm_drs_evaluate_swap(struct pd_port *pd_port, uint8_t role) +{ + pd_put_dpm_ack_event(pd_port); +} + +void pd_dpm_drs_change_role(struct pd_port *pd_port, uint8_t role) +{ + pd_set_data_role(pd_port, role); + + pd_port->pe_data.pe_ready = false; + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + pd_port->pe_data.pd_traffic_idle = false; +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + +#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY_DRSWAP + dpm_reaction_set(pd_port, DPM_REACTION_DFP_FLOW_DELAY); +#else + dpm_reaction_clear(pd_port, DPM_REACTION_DFP_FLOW_DELAY); +#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY_DRSWAP */ + + PE_STATE_DPM_INFORMED(pd_port); +} + +#endif /* CONFIG_USB_PD_DR_SWAP */ + +/* + * DRP : Power Role Swap + */ + +#ifdef CONFIG_USB_PD_PR_SWAP + +#if 0 +static bool pd_dpm_evaluate_source_cap_match(pd_port_t *pd_port) +{ + int i, j; + bool find_cap = false; + struct dpm_pdo_info_t sink, source; + struct pd_port_power_caps *snk_cap = &pd_port->local_snk_cap; + struct pd_port_power_caps *src_cap = &pd_port->pe_data.remote_src_cap; + + if ((src_cap->nr <= 0) || (snk_cap->nr <= 0)) + return false; + + for (j = 0; (j < snk_cap->nr) && (!find_cap); j++) { + dpm_extract_pdo_info(snk_cap->pdos[j], &sink); + + for (i = 0; (i < src_cap->nr) && (!find_cap); i++) { + dpm_extract_pdo_info(src_cap->pdos[i], &source); + + find_cap = dpm_is_valid_pdo_pair( + &sink, &source, pd_port->dpm_caps); + } + } + + return find_cap; +} +#endif + +/* + * Rules: + * External Sources -> EXS + * Provider/Consumers -> PC + * Consumers/Provider -> CP + * 1. PC (with EXS) shall always deny PR_SWAP from CP (without EXS) + * 2. PC (without EXS) shall always acppet PR_SWAP from CP (with EXS) + * unless the requester isn't able to provide PDOs. + */ + +int dpm_check_good_power(struct pd_port *pd_port) +{ + bool local_ex, partner_ex; + + local_ex = + (pd_port->dpm_caps & DPM_CAP_LOCAL_EXT_POWER) != 0; + + partner_ex = + (pd_port->pe_data.dpm_flags & DPM_FLAGS_PARTNER_EXTPOWER) != 0; + + if (local_ex != partner_ex) { + if (partner_ex) + return GOOD_PW_PARTNER; + return GOOD_PW_LOCAL; + } + + if (local_ex) + return GOOD_PW_BOTH; + + return GOOD_PW_NONE; +} + +void pd_dpm_prs_evaluate_swap(struct pd_port *pd_port, uint8_t role) +{ + int good_power; + bool sink, accept = true; + + bool check_src = (pd_port->dpm_caps & DPM_CAP_PR_SWAP_CHECK_GP_SRC) ? + true : false; + bool check_snk = (pd_port->dpm_caps & DPM_CAP_PR_SWAP_CHECK_GP_SNK) ? + true : false; + +#ifdef CONFIG_USB_PD_SRC_REJECT_PR_SWAP_IF_GOOD_PW + bool check_ext = + (pd_port->dpm_caps & DPM_CAP_CHECK_EXT_POWER) ? true : false; + + if (check_ext) + check_src = true; +#endif /* CONFIG_USB_PD_SRC_REJECT_PR_SWAP_IF_GOOD_PW */ + + if (check_src|check_snk) { + sink = pd_port->power_role == PD_ROLE_SINK; + good_power = dpm_check_good_power(pd_port); + + switch (good_power) { + case GOOD_PW_PARTNER: + if (sink && check_snk) + accept = false; + break; + + case GOOD_PW_LOCAL: + if ((!sink) && (check_src)) + accept = false; + break; + + default: + accept = true; + break; + } + } + + dpm_response_request(pd_port, accept); +} + +void pd_dpm_prs_turn_off_power_sink(struct pd_port *pd_port) +{ + /* iSnkSwapStdby : 2.5mA */ + tcpci_sink_vbus(pd_port->tcpc, + TCP_VBUS_CTRL_PR_SWAP, TCPC_VBUS_SINK_0V, 0); +} + +void pd_dpm_prs_enable_power_source(struct pd_port *pd_port, bool en) +{ + int vbus_level = en ? TCPC_VBUS_SOURCE_5V : TCPC_VBUS_SOURCE_0V; + + tcpci_source_vbus(pd_port->tcpc, + TCP_VBUS_CTRL_PR_SWAP, vbus_level, -1); + + if (en) + pd_enable_vbus_valid_detection(pd_port, en); + else + pd_enable_vbus_safe0v_detection(pd_port); +} + +void pd_dpm_prs_change_role(struct pd_port *pd_port, uint8_t role) +{ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + pd_port->pe_data.pd_traffic_idle = false; +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + + dpm_reaction_clear(pd_port, DPM_REACTION_REQUEST_PR_SWAP); + pd_set_power_role(pd_port, role); + pd_put_dpm_ack_event(pd_port); +} + +#endif /* CONFIG_USB_PD_PR_SWAP */ + +/* + * DRP : Vconn Swap + */ + +#ifdef CONFIG_USB_PD_VCONN_SWAP + +void pd_dpm_vcs_evaluate_swap(struct pd_port *pd_port) +{ + bool accept = true; + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + struct tcpc_device *tcpc = pd_port->tcpc; + + /* Reject it if we don't want supply vconn */ + if ((!pd_port->vconn_role) && + (tcpc->tcpc_vconn_supply == TCPC_VCONN_SUPPLY_NEVER)) + accept = false; +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + + dpm_response_request(pd_port, accept); +} + +void pd_dpm_vcs_enable_vconn(struct pd_port *pd_port, uint8_t role) +{ + pd_set_vconn(pd_port, role); + + /* If we can't enable vconn immediately, + * then after vconn_on, + * Vconn Controller should pd_put_dpm_ack_event() + */ + +#if CONFIG_USB_PD_VCONN_READY_TOUT + if (role != PD_ROLE_VCONN_OFF) { + pd_enable_timer(pd_port, PD_TIMER_VCONN_READY); + return; + } +#endif /* CONFIG_USB_PD_VCONN_READY_TOUT */ + + PE_STATE_DPM_ACK_IMMEDIATELY(pd_port); +} + +#endif /* CONFIG_USB_PD_VCONN_SWAP */ + +/* + * PE : PD3.0 + */ + +#ifdef CONFIG_USB_PD_REV30 + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE +void pd_dpm_inform_source_cap_ext(struct pd_port *pd_port) +{ + struct pd_source_cap_ext *scedb; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dpm_check_ext_msg_event(pd_port, PD_EXT_SOURCE_CAP_EXT)) { + scedb = pd_get_msg_data_payload(pd_port); + DPM_INFO2("vid=0x%04x, pid=0x%04x\n", scedb->vid, scedb->pid); + DPM_INFO2("fw_ver=0x%02x, hw_ver=0x%02x\n", + scedb->fw_ver, scedb->hw_ver); + + dpm_reaction_clear(pd_port, + DPM_REACTION_GET_SOURCE_CAP_EXT); + } +} +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL +int pd_dpm_send_source_cap_ext(struct pd_port *pd_port) +{ + return pd_send_sop_ext_msg(pd_port, PD_EXT_SOURCE_CAP_EXT, + PD_SCEDB_SIZE, &pd_port->src_cap_ext); +} +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL + +static const struct pd_battery_capabilities c_invalid_bcdb = { + 0, 0, 0, 0, PD_BCDB_BAT_TYPE_INVALID +}; + +int pd_dpm_send_battery_cap(struct pd_port *pd_port) +{ + struct pd_battery_info *bat_info; + const struct pd_battery_capabilities *bcdb; + struct pd_get_battery_capabilities *gbcdb = + pd_get_msg_data_payload(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_INFO2("bat_ref=%d\n", gbcdb->bat_cap_ref); + + bat_info = pd_get_battery_info(pd_port, gbcdb->bat_cap_ref); + + if (bat_info != NULL) { + tcpci_notify_request_bat_info( + tcpc, gbcdb->bat_cap_ref); + bcdb = &bat_info->bat_cap; + } else + bcdb = &c_invalid_bcdb; + + return pd_send_sop_ext_msg(pd_port, PD_EXT_BAT_CAP, + PD_BCDB_SIZE, bcdb); +} +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE +void pd_dpm_inform_battery_cap(struct pd_port *pd_port) +{ + struct pd_battery_capabilities *bcdb; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dpm_check_ext_msg_event(pd_port, PD_EXT_BAT_CAP)) { + bcdb = pd_get_msg_data_payload(pd_port); + DPM_INFO2("vid=0x%04x, pid=0x%04x\n", + bcdb->vid, bcdb->pid); + } +} +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL + +static const uint32_t c_invalid_bsdo = + BSDO(0xffff, BSDO_BAT_INFO_INVALID_REF); + +int pd_dpm_send_battery_status(struct pd_port *pd_port) +{ + const uint32_t *bsdo; + struct pd_battery_info *bat_info; + struct pd_get_battery_status *gbsdb = + pd_get_msg_data_payload(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_INFO2("bat_ref=%d\n", gbsdb->bat_status_ref); + + bat_info = pd_get_battery_info(pd_port, gbsdb->bat_status_ref); + + if (bat_info != NULL) { + tcpci_notify_request_bat_info( + tcpc, gbsdb->bat_status_ref); + bsdo = &bat_info->bat_status; + } else + bsdo = &c_invalid_bsdo; + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + pd_port->pe_data.get_status_once = true; +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ + + return pd_send_sop_data_msg(pd_port, + PD_DATA_BAT_STATUS, PD_BSDO_SIZE, bsdo); +} +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ + + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE +void pd_dpm_inform_battery_status(struct pd_port *pd_port) +{ + uint32_t *payload; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dpm_check_data_msg_event(pd_port, PD_DATA_BAT_STATUS)) { + payload = pd_get_msg_data_payload(pd_port); + DPM_INFO2("0x%08x\n", payload[0]); + } +} +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ + +static const struct pd_manufacturer_info c_invalid_mfrs = { + .vid = 0, .pid = 0, .mfrs_string = "Not Supported", +}; + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL +int pd_dpm_send_mfrs_info(struct pd_port *pd_port) +{ + uint8_t len = 0; + struct pd_battery_info *bat_info; + const struct pd_manufacturer_info *midb = NULL; + + struct pd_get_manufacturer_info *gmidb = + pd_get_msg_data_payload(pd_port); + + if (gmidb->info_target == PD_GMIDB_TARGET_PORT) + midb = &pd_port->mfrs_info; + + if (gmidb->info_target == PD_GMIDB_TARGET_BATTRY) { + bat_info = pd_get_battery_info(pd_port, gmidb->info_ref); + if (bat_info) + midb = &bat_info->mfrs_info; + } + + if (midb == NULL) + midb = &c_invalid_mfrs; + + len = strnlen((char *)midb->mfrs_string, sizeof(midb->mfrs_string)); + if (len < sizeof(midb->mfrs_string)) + len++; + return pd_send_sop_ext_msg(pd_port, PD_EXT_MFR_INFO, + PD_MIDB_MIN_SIZE + len, midb); +} +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE +void pd_dpm_inform_mfrs_info(struct pd_port *pd_port) +{ + struct pd_manufacturer_info *midb; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dpm_check_ext_msg_event(pd_port, PD_EXT_MFR_INFO)) { + midb = pd_get_msg_data_payload(pd_port); + DPM_INFO2("vid=0x%x, pid=0x%x\n", midb->vid, midb->pid); + } +} +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ + + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE +void pd_dpm_inform_country_codes(struct pd_port *pd_port) +{ + struct pd_country_codes *ccdb; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dpm_check_ext_msg_event(pd_port, PD_EXT_COUNTRY_CODES)) { + ccdb = pd_get_msg_data_payload(pd_port); + DPM_INFO2("len=%d, country_code[0]=0x%04x\n", + ccdb->length, ccdb->country_code[0]); + } +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ + + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL +int pd_dpm_send_country_codes(struct pd_port *pd_port) +{ + uint8_t i; + struct pd_country_codes ccdb; + struct pd_country_authority *ca = pd_port->country_info; + + ccdb.length = pd_port->country_nr; + + for (i = 0; i < ccdb.length; i++) + ccdb.country_code[i] = ca[i].code; + + return pd_send_sop_ext_msg(pd_port, PD_EXT_COUNTRY_CODES, + 2 + ccdb.length*2, &ccdb); +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE +void pd_dpm_inform_country_info(struct pd_port *pd_port) +{ + struct pd_country_info *cidb; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dpm_check_ext_msg_event(pd_port, PD_EXT_COUNTRY_INFO)) { + cidb = pd_get_msg_data_payload(pd_port); + DPM_INFO2("cc=0x%04x, ci=%d\n", + cidb->country_code, cidb->country_special_data[0]); + } +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL +int pd_dpm_send_country_info(struct pd_port *pd_port) +{ + uint8_t i, cidb_size; + struct pd_country_info cidb; + struct pd_country_authority *ca = pd_port->country_info; + uint32_t *pccdo = pd_get_msg_data_payload(pd_port); + uint16_t cc = CCDO_COUNTRY_CODE(*pccdo); + + cidb_size = PD_CIDB_MIN_SIZE; + cidb.country_code = cc; + cidb.reserved = 0; + cidb.country_special_data[0] = 0; + + for (i = 0; i < pd_port->country_nr; i++) { + if (ca[i].code == cc) { + cidb_size += ca[i].len; + memcpy(cidb.country_special_data, + ca[i].data, ca[i].len); + break; + } + } + + return pd_send_sop_ext_msg(pd_port, PD_EXT_COUNTRY_INFO, + cidb_size, &cidb); +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +void pd_dpm_inform_alert(struct pd_port *pd_port) +{ + uint32_t *data = pd_get_msg_data_payload(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_INFO("inform_alert:0x%08x\n", data[0]); + + pd_port->pe_data.pd_traffic_idle = false; + pd_port->pe_data.remote_alert = data[0]; + tcpci_notify_alert(pd_port->tcpc, data[0]); +} +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL +int pd_dpm_send_alert(struct pd_port *pd_port) +{ + uint32_t ado = pd_port->pe_data.local_alert; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + pd_port->pe_data.local_alert = 0; + DPM_INFO("send_alert:0x%08x\n", ado); + + return pd_send_sop_data_msg(pd_port, PD_DATA_ALERT, + PD_ADO_SIZE, &ado); +} +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE +void pd_dpm_inform_status(struct pd_port *pd_port) +{ + struct pd_status *sdb; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dpm_check_ext_msg_event(pd_port, PD_EXT_STATUS)) { + sdb = pd_get_msg_data_payload(pd_port); + DPM_INFO2("Temp=%d, IN=0x%x, BAT_IN=0x%x, EVT=0x%x, PTF=0x%x\n", + sdb->internal_temp, sdb->present_input, + sdb->present_battey_input, sdb->event_flags, + PD_STATUS_TEMP_PTF(sdb->temp_status)); + + tcpci_notify_status(tcpc, sdb); + } +} +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL +int pd_dpm_send_status(struct pd_port *pd_port) +{ + struct pd_status sdb; + struct pe_data *pe_data = &pd_port->pe_data; + + memset(&sdb, 0, sizeof(struct pd_status)); + + sdb.present_input = pd_port->pd_status_present_in; + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO + if (sdb.present_input & + PD_STATUS_INPUT_INT_POWER_BAT) { + sdb.present_battey_input = pd_port->pd_status_bat_in; + } +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + + sdb.event_flags = pe_data->pd_status_event; + pe_data->pd_status_event &= ~PD_STASUS_EVENT_READ_CLEAR; + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL_TEMP + sdb.internal_temp = pd_port->pd_status_temp; + sdb.temp_status = pd_port->pd_status_temp_status; +#else + sdb.internal_temp = 0; + sdb.temp_status = 0; +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL_TEMP */ + + if (sdb.event_flags & PD_STATUS_EVENT_OTP) + sdb.temp_status = PD_STATUS_TEMP_SET_PTF(PD_PTF_OVER_TEMP); + + if (pd_port->power_role != PD_ROLE_SINK) + sdb.event_flags &= ~PD_STATUS_EVENT_OVP; + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + pe_data->get_status_once = true; +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ + + return pd_send_sop_ext_msg(pd_port, PD_EXT_STATUS, + PD_SDB_SIZE, &sdb); +} +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +void pd_dpm_inform_pps_status(struct pd_port *pd_port) +{ + struct pd_pps_status_raw *ppssdb; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (dpm_check_ext_msg_event(pd_port, PD_EXT_PPS_STATUS)) { + ppssdb = pd_get_msg_data_payload(pd_port); + DPM_INFO2("mv=%d, ma=%d\n", + PD_PPS_GET_OUTPUT_MV(ppssdb->output_vol_raw), + PD_PPS_GET_OUTPUT_MA(ppssdb->output_curr_raw)); + } +} +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +void pd_dpm_inform_not_support(struct pd_port *pd_port) +{ + /* TODO */ +} + +#endif /* CONFIG_USB_PD_REV30 */ + +/* + * PE : Dynamic Control Vconn + */ + +void pd_dpm_dynamic_enable_vconn(struct pd_port *pd_port) +{ +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + struct tcpc_device *tcpc = pd_port->tcpc; + + if (tcpc->tcpc_vconn_supply <= TCPC_VCONN_SUPPLY_ALWAYS) + return; + + if (pd_port->vconn_role == PD_ROLE_VCONN_DYNAMIC_OFF) { + DPM_INFO2("DynamicVCEn\n"); + pd_set_vconn(pd_port, PD_ROLE_VCONN_DYNAMIC_ON); + } +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ +} + +void pd_dpm_dynamic_disable_vconn(struct pd_port *pd_port) +{ +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + bool keep_vconn; + struct tcpc_device *tcpc = pd_port->tcpc; + + if (!pd_port->vconn_role) + return; + + switch (tcpc->tcpc_vconn_supply) { + case TCPC_VCONN_SUPPLY_EMARK_ONLY: + keep_vconn = pd_port->pe_data.power_cable_present; + break; + case TCPC_VCONN_SUPPLY_STARTUP: + keep_vconn = false; + break; + default: + keep_vconn = true; + break; + } + + if (keep_vconn) + return; + + if (tcpc->tcp_event_count) + return; + + if (pd_port->vconn_role != PD_ROLE_VCONN_DYNAMIC_OFF) { + DPM_INFO2("DynamicVCDis\n"); + pd_set_vconn(pd_port, PD_ROLE_VCONN_DYNAMIC_OFF); + } +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ +} + +/* + * PE : Notify DPM + */ + +int pd_dpm_notify_pe_startup(struct pd_port *pd_port) +{ + uint32_t reactions = DPM_REACTION_CAP_ALWAYS; + +#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY_STARTUP + reactions |= DPM_REACTION_DFP_FLOW_DELAY; +#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY_STARTUP */ + +#ifdef CONFIG_USB_PD_UFP_FLOW_DELAY + reactions |= DPM_REACTION_UFP_FLOW_DELAY; +#endif /* CONFIG_USB_PD_UFP_FLOW_DELAY */ + +#ifdef CONFIG_USB_PD_SRC_TRY_PR_SWAP_IF_BAD_PW + reactions |= DPM_REACTION_ATTEMPT_GET_FLAG | + DPM_REACTION_REQUEST_PR_SWAP; +#else + if (DPM_CAP_EXTRACT_PR_CHECK(pd_port->dpm_caps)) { + reactions |= DPM_REACTION_REQUEST_PR_SWAP; + if (DPM_CAP_EXTRACT_PR_CHECK(pd_port->dpm_caps) == + DPM_CAP_PR_CHECK_PREFER_SNK) + reactions |= DPM_REACTION_ATTEMPT_GET_FLAG; + } + + if (pd_port->dpm_caps & DPM_CAP_CHECK_EXT_POWER) + reactions |= DPM_REACTION_ATTEMPT_GET_FLAG; +#endif /* CONFIG_USB_PD_SRC_TRY_PR_SWAP_IF_BAD_PW */ + + if (DPM_CAP_EXTRACT_DR_CHECK(pd_port->dpm_caps)) { + reactions |= DPM_REACTION_REQUEST_DR_SWAP; + if (DPM_CAP_EXTRACT_DR_CHECK(pd_port->dpm_caps) == + DPM_CAP_DR_CHECK_PREFER_UFP) + reactions |= DPM_REACTION_ATTEMPT_GET_FLAG; + } + + if (pd_port->dpm_caps & DPM_CAP_ATTEMP_DISCOVER_CABLE) + reactions |= DPM_REACTION_CAP_DISCOVER_CABLE; + + if (pd_port->dpm_caps & DPM_CAP_ATTEMP_DISCOVER_CABLE_DFP) + reactions |= DPM_REACTION_DISCOVER_CABLE_FLOW; + +#ifdef CONFIG_USB_PD_ATTEMP_ENTER_MODE + reactions |= DPM_REACTION_DISCOVER_ID | + DPM_REACTION_DISCOVER_SVID; +#else + if (pd_port->dpm_caps & DPM_CAP_ATTEMP_DISCOVER_ID) + reactions |= DPM_REACTION_DISCOVER_ID; + if (pd_port->dpm_caps & DPM_CAP_ATTEMP_DISCOVER_SVID) + reactions |= DPM_REACTION_DISCOVER_SVID; +#endif /* CONFIG_USB_PD_ATTEMP_ENTER_MODE */ + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + reactions |= DPM_REACTION_GET_SOURCE_CAP_EXT; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ + + dpm_reaction_set(pd_port, reactions); + + svdm_reset_state(pd_port); + svdm_notify_pe_startup(pd_port); + return 0; + +} + +int pd_dpm_notify_pe_hardreset(struct pd_port *pd_port) +{ + struct pe_data *pe_data = &pd_port->pe_data; + + svdm_reset_state(pd_port); + + pe_data->pe_ready = false; + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + pe_data->pd_traffic_idle = false; +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + + if (pe_data->dpm_svdm_retry_cnt >= CONFIG_USB_PD_DPM_SVDM_RETRY) + return 0; + + pe_data->dpm_svdm_retry_cnt++; + +#ifdef CONFIG_USB_PD_ATTEMP_ENTER_MODE + dpm_reaction_set(pd_port, DPM_REACTION_DISCOVER_ID | + DPM_REACTION_DISCOVER_SVID); +#endif /* CONFIG_USB_PD_ATTEMP_ENTER_MODE */ + + svdm_notify_pe_startup(pd_port); + return 0; +} + +/* + * SVDM + */ + +static inline bool dpm_register_svdm_ops(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, const struct svdm_svid_ops *ops) +{ + bool ret = true; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (ops->parse_svid_data) + ret = ops->parse_svid_data(pd_port, svid_data); + + if (ret) { + svid_data->ops = ops; + svid_data->svid = ops->svid; + DPM_DBG("register_svdm: 0x%x\n", ops->svid); + } + + return ret; +} + +struct svdm_svid_data *dpm_get_svdm_svid_data( + struct pd_port *pd_port, uint16_t svid) +{ + uint8_t i; + struct svdm_svid_data *svid_data; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + if (svid_data->svid == svid) + return svid_data; + } + + return NULL; +} + +bool svdm_reset_state(struct pd_port *pd_port) +{ + int i; + struct svdm_svid_data *svid_data; + + pd_port->dpm_charging_policy = pd_port->dpm_charging_policy_default; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + if (svid_data->ops && svid_data->ops->reset_state) + svid_data->ops->reset_state(pd_port, svid_data); + } + + return true; +} + +bool svdm_notify_pe_startup(struct pd_port *pd_port) +{ + int i; + struct svdm_svid_data *svid_data; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + if (svid_data->ops && svid_data->ops->notify_pe_startup) + svid_data->ops->notify_pe_startup(pd_port, svid_data); + } + + return true; +} + +/* + * dpm_core_init + */ + +int pd_dpm_core_init(struct pd_port *pd_port) +{ + int i, j; + bool ret; + uint8_t svid_ops_nr = ARRAY_SIZE(svdm_svid_ops); + struct tcpc_device *tcpc = pd_port->tcpc; + + pd_port->svid_data = devm_kzalloc(&pd_port->tcpc->dev, + sizeof(struct svdm_svid_data) * svid_ops_nr, GFP_KERNEL); + + if (!pd_port->svid_data) + return -ENOMEM; + + for (i = 0, j = 0; i < svid_ops_nr; i++) { + ret = dpm_register_svdm_ops(pd_port, + &pd_port->svid_data[j], &svdm_svid_ops[i]); + + if (ret) + j++; + } + + pd_port->svid_data_cnt = j; + +#ifdef CONFIG_USB_PD_REV30 + pd_port->pps_request_wake_lock = + wakeup_source_register(&tcpc->dev, "pd_pps_request_wake_lock"); + init_waitqueue_head(&pd_port->pps_request_wait_que); + atomic_set(&pd_port->pps_request, false); + pd_port->pps_request_task = kthread_run(pps_request_thread_fn, tcpc, + "pps_request_%s", + tcpc->desc.name); +#endif /* CONFIG_USB_PD_REV30 */ + + return 0; +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_dpm_pdo_select.c b/drivers/usb/typec/mmi_tcpc/pd_dpm_pdo_select.c new file mode 100644 index 000000000000..5332f4c35604 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_dpm_pdo_select.c @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Core Driver + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_dpm_pdo_select.h" + +#ifdef CONFIG_USB_POWER_DELIVERY + +struct dpm_select_info_t { + uint8_t pos; + int max_uw; + int cur_mv; + uint8_t policy; +}; + +static inline void dpm_extract_apdo_info( + uint32_t pdo, struct dpm_pdo_info_t *info) +{ +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + switch (APDO_TYPE(pdo)) { + case APDO_TYPE_PPS: + info->apdo_type = DPM_APDO_TYPE_PPS; + + if (pdo & APDO_PPS_CURR_FOLDBACK) + info->apdo_type |= DPM_APDO_TYPE_PPS_CF; + + info->pwr_limit = APDO_PPS_EXTRACT_PWR_LIMIT(pdo); + info->ma = APDO_PPS_EXTRACT_CURR(pdo); + info->vmin = APDO_PPS_EXTRACT_MIN_VOLT(pdo); + info->vmax = APDO_PPS_EXTRACT_MAX_VOLT(pdo); + info->uw = info->ma * info->vmax; + return; + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + info->type = TCPM_POWER_CAP_VAL_TYPE_UNKNOWN; +} + +void dpm_extract_pdo_info( + uint32_t pdo, struct dpm_pdo_info_t *info) +{ + memset(info, 0, sizeof(struct dpm_pdo_info_t)); + + info->type = PDO_TYPE_VAL(pdo); + + switch (PDO_TYPE(pdo)) { + case PDO_TYPE_FIXED: + info->ma = PDO_FIXED_EXTRACT_CURR(pdo); + info->vmax = info->vmin = PDO_FIXED_EXTRACT_VOLT(pdo); + info->uw = info->ma * info->vmax; + break; + case PDO_TYPE_VARIABLE: + info->ma = PDO_VAR_EXTRACT_CURR(pdo); + info->vmin = PDO_VAR_EXTRACT_MIN_VOLT(pdo); + info->vmax = PDO_VAR_EXTRACT_MAX_VOLT(pdo); + info->uw = info->ma * info->vmax; + break; + + case PDO_TYPE_BATTERY: + info->uw = PDO_BATT_EXTRACT_OP_POWER(pdo) * 1000; + info->vmin = PDO_BATT_EXTRACT_MIN_VOLT(pdo); + info->vmax = PDO_BATT_EXTRACT_MAX_VOLT(pdo); + info->ma = info->uw / info->vmin; + break; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + case PDO_TYPE_APDO: + dpm_extract_apdo_info(pdo, info); + break; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + } +} + +#ifndef MIN +#define MIN(a, b) ((a < b) ? (a) : (b)) +#endif + +static inline int dpm_calc_src_cap_power_uw( + struct dpm_pdo_info_t *source, struct dpm_pdo_info_t *sink) +{ + int uw, ma; + + if (source->type == DPM_PDO_TYPE_BAT) { + uw = source->uw; + + if (sink->type == DPM_PDO_TYPE_BAT) + uw = MIN(uw, sink->uw); + } else { + ma = source->ma; + + if (sink->type != DPM_PDO_TYPE_BAT) + ma = MIN(ma, sink->ma); + + uw = ma * source->vmax; + } + + return uw; +} + +/* + * Select PDO from VSafe5V + */ + +static bool dpm_select_pdo_from_vsafe5v( + struct dpm_select_info_t *select_info, + struct dpm_pdo_info_t *sink, struct dpm_pdo_info_t *source) +{ + int uw; + + if ((sink->vmax != TCPC_VBUS_SINK_5V) || + (sink->vmin != TCPC_VBUS_SINK_5V) || + (source->vmax != TCPC_VBUS_SINK_5V) || + (source->vmin != TCPC_VBUS_SINK_5V)) + return false; + + uw = dpm_calc_src_cap_power_uw(source, sink); + if (uw > select_info->max_uw) { + select_info->max_uw = uw; + select_info->cur_mv = source->vmax; + return true; + } + + return false; +} + +/* + * Select PDO from Direct Charge + */ + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC +static bool dpm_select_pdo_from_direct_charge( + struct dpm_select_info_t *select_info, + struct dpm_pdo_info_t *sink, struct dpm_pdo_info_t *source) +{ + int uw; + + if (sink->type != DPM_PDO_TYPE_VAR + || source->type != DPM_PDO_TYPE_VAR) + return false; + + if (source->vmin >= TCPC_VBUS_SINK_5V) + return false; + + if (sink->vmax < source->vmax) + return false; + + if (sink->vmin > source->vmin) + return false; + + uw = dpm_calc_src_cap_power_uw(source, sink); + if (uw > select_info->max_uw) { + select_info->max_uw = uw; + select_info->cur_mv = source->vmax; + return true; + } + + return false; +} +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +/* + * Select PDO from Custom + */ + +static bool dpm_select_pdo_from_custom( + struct dpm_select_info_t *select_info, + struct dpm_pdo_info_t *sink, struct dpm_pdo_info_t *source) +{ + /* TODO */ + return dpm_select_pdo_from_vsafe5v(select_info, sink, source); +} + +/* + * Select PDO from Max Power + */ + +static inline bool dpm_is_valid_pdo_pair(struct dpm_pdo_info_t *sink, + struct dpm_pdo_info_t *source, uint32_t policy) +{ + if (sink->vmax < source->vmax) + return false; + + if (sink->vmin > source->vmin) + return false; + + if (policy & DPM_CHARGING_POLICY_IGNORE_MISMATCH_CURR) + return true; + + return sink->ma <= source->ma; +} + +static bool dpm_select_pdo_from_max_power( + struct dpm_select_info_t *select_info, + struct dpm_pdo_info_t *sink, struct dpm_pdo_info_t *source) +{ + bool overload; + int uw; + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC + /* Variable for direct charge only */ + if ((sink->type == DPM_PDO_TYPE_VAR) && (sink->vmin < 5000)) + return false; +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +#ifdef CONFIG_USB_PD_REV30 + if (sink->type == DPM_PDO_TYPE_APDO) + return false; +#endif /* CONFIG_USB_PD_REV30 */ + + if (!dpm_is_valid_pdo_pair(sink, source, select_info->policy)) + return false; + + uw = dpm_calc_src_cap_power_uw(source, sink); + + overload = uw > select_info->max_uw; + + if ((!overload) && (uw == select_info->max_uw)) { + if (select_info->policy & + DPM_CHARGING_POLICY_PREFER_LOW_VOLTAGE) + overload = (source->vmax < select_info->cur_mv); + else if (select_info->policy & + DPM_CHARGING_POLICY_PREFER_HIGH_VOLTAGE) + overload = (source->vmax > select_info->cur_mv); + } + + if (overload) { + select_info->max_uw = uw; + select_info->cur_mv = source->vmax; + return true; + } + + return false; +} + +/* + * Select PDO from PPS + */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +static bool dpm_select_pdo_from_pps( + struct dpm_select_info_t *select_info, + struct dpm_pdo_info_t *sink, struct dpm_pdo_info_t *source) +{ + bool overload; + int uw, diff_mv; + const int tolerance = 300; /* 5900 * 5% */ + + if (sink->type != DPM_PDO_TYPE_APDO || + source->type != DPM_PDO_TYPE_APDO) + return false; + + if (!(source->apdo_type & DPM_APDO_TYPE_PPS)) + return false; + + if (sink->vmax > source->vmax) + return false; + + if (sink->vmin < source->vmin) + return false; + + if (!(select_info->policy & DPM_CHARGING_POLICY_IGNORE_MISMATCH_CURR)) { + if (source->ma < sink->ma) + return false; + } + + uw = sink->vmax * source->ma; + diff_mv = source->vmax - sink->vmax; + + if (uw > select_info->max_uw) + overload = true; + else if (uw < select_info->max_uw) + overload = false; + else if ((select_info->cur_mv < tolerance) && (diff_mv > tolerance)) + overload = true; + else if (diff_mv < select_info->cur_mv) + overload = true; + else + overload = false; + + if (overload) { + select_info->max_uw = uw; + select_info->cur_mv = diff_mv; + return true; + } + + return false; +} +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +/* + * Select PDO from defined rule ... + */ + +typedef bool (*dpm_select_pdo_fun)( + struct dpm_select_info_t *select_info, + struct dpm_pdo_info_t *sink, struct dpm_pdo_info_t *source); + +bool dpm_find_match_req_info(struct dpm_rdo_info_t *req_info, + struct dpm_pdo_info_t *sink, int cnt, uint32_t *src_pdos, + int min_uw, uint32_t policy) +{ + int i; + struct dpm_select_info_t select; + struct dpm_pdo_info_t source; + dpm_select_pdo_fun select_pdo_fun; + + select.pos = 0; + select.cur_mv = 0; + select.max_uw = min_uw; + select.policy = policy; + + switch (policy & DPM_CHARGING_POLICY_MASK) { + case DPM_CHARGING_POLICY_MAX_POWER: + select_pdo_fun = dpm_select_pdo_from_max_power; + break; + + case DPM_CHARGING_POLICY_CUSTOM: + select_pdo_fun = dpm_select_pdo_from_custom; + break; + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC + case DPM_CHARGING_POLICY_DIRECT_CHARGE: + select_pdo_fun = dpm_select_pdo_from_direct_charge; + break; +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + case DPM_CHARGING_POLICY_PPS: + select_pdo_fun = dpm_select_pdo_from_pps; + break; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + default: /* DPM_CHARGING_POLICY_VSAFE5V */ + select_pdo_fun = dpm_select_pdo_from_vsafe5v; + break; + } + + for (i = 0; i < cnt; i++) { + dpm_extract_pdo_info(src_pdos[i], &source); + + if (select_pdo_fun(&select, sink, &source)) + select.pos = i+1; + } + + if (select.pos > 0) { + dpm_extract_pdo_info(src_pdos[select.pos-1], &source); + + req_info->pos = select.pos; + req_info->type = source.type; + req_info->vmax = source.vmax; + req_info->vmin = source.vmin; + + if (sink->type == DPM_PDO_TYPE_BAT) + req_info->mismatch = select.max_uw < sink->uw; + else + req_info->mismatch = source.ma < sink->ma; + + if (source.type == DPM_PDO_TYPE_BAT) { + req_info->max_uw = sink->uw; + req_info->oper_uw = select.max_uw; + } else { + req_info->max_ma = sink->ma; + req_info->oper_ma = MIN(sink->ma, source.ma); + } + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + if (source.type == DPM_PDO_TYPE_APDO) { + req_info->vmax = sink->vmax; + req_info->vmin = sink->vmin; + } +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + return true; + } + + return false; +} +#endif /* CONFIG_USB_POWER_DELIVERY */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_dpm_prv.h b/drivers/usb/typec/mmi_tcpc/pd_dpm_prv.h new file mode 100644 index 000000000000..d33ac19f07b9 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_dpm_prv.h @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PD_DPM_PRV_H_INCLUDED +#define PD_DPM_PRV_H_INCLUDED + +#include +#include + +#define SVID_DATA_LOCAL_MODE(svid_data, n) \ + (svid_data->local_mode.mode_vdo[n]) + +#define SVID_DATA_REMOTE_MODE(svid_data, n) \ + (svid_data->remote_mode.mode_vdo[n]) + +#define SVID_DATA_DFP_GET_ACTIVE_MODE(svid_data)\ + SVID_DATA_REMOTE_MODE(svid_data, svid_data->active_mode-1) + +#define SVID_DATA_UFP_GET_ACTIVE_MODE(svid_data)\ + SVID_DATA_LOCAL_MODE(svid_data, svid_data->active_mode-1) + +extern int dpm_check_supported_modes(void); + +struct svdm_svid_ops { + const char *name; + uint16_t svid; + + bool (*dfp_inform_id)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); + bool (*dfp_inform_svids)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); + bool (*dfp_inform_modes)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); + + bool (*dfp_inform_enter_mode)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops, bool ack); + bool (*dfp_inform_exit_mode)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops); + + bool (*dfp_inform_attention)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + + void (*ufp_request_enter_mode)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops); + void (*ufp_request_exit_mode)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, uint8_t ops); + + bool (*notify_pe_startup)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + int (*notify_pe_ready)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + bool (*notify_pe_shutdown)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + bool (*dfp_notify_uvdm)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack); + + bool (*ufp_notify_uvdm)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); +#endif + + bool (*reset_state)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); + + bool (*parse_svid_data)(struct pd_port *pd_port, + struct svdm_svid_data *svid_data); +}; + +static inline bool dpm_check_data_msg_event( + struct pd_port *pd_port, uint8_t msg) +{ + return pd_event_data_msg_match( + pd_get_curr_pd_event(pd_port), msg); +} + +#ifdef CONFIG_USB_PD_REV30 +static inline bool dpm_check_ext_msg_event( + struct pd_port *pd_port, uint8_t msg) +{ + return pd_event_ext_msg_match( + pd_get_curr_pd_event(pd_port), msg); +} +#endif /* CONFIG_USB_PD_REV30 */ + +static inline uint8_t dpm_vdm_get_ops(struct pd_port *pd_port) +{ + return pd_port->curr_vdm_ops; +} + +static inline uint16_t dpm_vdm_get_svid(struct pd_port *pd_port) +{ + return pd_port->curr_vdm_svid; +} + +static inline int dpm_vdm_reply_svdm_request( + struct pd_port *pd_port, bool ack) +{ + return pd_reply_svdm_request_simply( + pd_port, ack ? CMDT_RSP_ACK : CMDT_RSP_NAK); +} + +static inline int dpm_vdm_reply_svdm_nak(struct pd_port *pd_port) +{ + return pd_reply_svdm_request_simply(pd_port, CMDT_RSP_NAK); +} + +enum { + GOOD_PW_NONE = 0, /* both no GP */ + GOOD_PW_PARTNER, /* partner has GP */ + GOOD_PW_LOCAL, /* local has GP */ + GOOD_PW_BOTH, /* both have GPs */ +}; + +int dpm_check_good_power(struct pd_port *pd_port); + +/* SVDM */ + +extern struct svdm_svid_data * + dpm_get_svdm_svid_data(struct pd_port *pd_port, uint16_t svid); + +extern bool svdm_reset_state(struct pd_port *pd_port); +extern bool svdm_notify_pe_startup(struct pd_port *pd_port); + +static inline int svdm_notify_pe_ready(struct pd_port *pd_port) +{ + int i, ret; + struct svdm_svid_data *svid_data; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + if (svid_data->ops && svid_data->ops->notify_pe_ready) { + ret = svid_data->ops->notify_pe_ready( + pd_port, svid_data); + if (ret != 0) + return ret; + } + } + + return 0; +} + +static inline bool svdm_notify_pe_shutdown( + struct pd_port *pd_port) +{ + int i; + struct svdm_svid_data *svid_data; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + if (svid_data->ops && svid_data->ops->notify_pe_shutdown) { + svid_data->ops->notify_pe_shutdown( + pd_port, svid_data); + } + } + + return 0; +} + +static inline bool svdm_dfp_inform_id(struct pd_port *pd_port, bool ack) +{ + int i; + struct svdm_svid_data *svid_data; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + if (svid_data->ops && svid_data->ops->dfp_inform_id) + svid_data->ops->dfp_inform_id(pd_port, svid_data, ack); + } + + return true; +} + +static inline bool svdm_dfp_inform_svids(struct pd_port *pd_port, bool ack) +{ + int i; + struct svdm_svid_data *svid_data; + + for (i = 0; i < pd_port->svid_data_cnt; i++) { + svid_data = &pd_port->svid_data[i]; + if (svid_data->ops && svid_data->ops->dfp_inform_svids) + svid_data->ops->dfp_inform_svids( + pd_port, svid_data, ack); + } + + return true; +} + +static inline bool svdm_dfp_inform_modes( + struct pd_port *pd_port, uint16_t svid, bool ack) +{ + struct svdm_svid_data *svid_data; + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return false; + + if (svid_data->ops && svid_data->ops->dfp_inform_modes) + svid_data->ops->dfp_inform_modes(pd_port, svid_data, ack); + + return true; +} + +static inline bool svdm_dfp_inform_enter_mode( + struct pd_port *pd_port, uint16_t svid, uint8_t ops, bool ack) +{ + struct svdm_svid_data *svid_data; + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return false; + + if (svid_data->ops && svid_data->ops->dfp_inform_enter_mode) + svid_data->ops->dfp_inform_enter_mode( + pd_port, svid_data, ops, ack); + + return true; +} + +static inline bool svdm_dfp_inform_exit_mode( + struct pd_port *pd_port, uint16_t svid, uint8_t ops) +{ + struct svdm_svid_data *svid_data; + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return false; + + if (svid_data->ops && svid_data->ops->dfp_inform_exit_mode) + svid_data->ops->dfp_inform_exit_mode(pd_port, svid_data, ops); + + return true; +} + +static inline bool svdm_dfp_inform_attention( + struct pd_port *pd_port, uint16_t svid) +{ + struct svdm_svid_data *svid_data; + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return false; + + if (svid_data->ops && svid_data->ops->dfp_inform_attention) + svid_data->ops->dfp_inform_attention(pd_port, svid_data); + + return true; +} + +static inline bool svdm_ufp_request_enter_mode( + struct pd_port *pd_port, uint16_t svid, uint8_t ops) +{ + struct svdm_svid_data *svid_data; + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return false; + + if (svid_data->ops && svid_data->ops->ufp_request_enter_mode) + svid_data->ops->ufp_request_enter_mode(pd_port, svid_data, ops); + + return true; +} + +static inline bool svdm_ufp_request_exit_mode( + struct pd_port *pd_port, uint16_t svid, uint8_t ops) +{ + struct svdm_svid_data *svid_data; + + svid_data = dpm_get_svdm_svid_data(pd_port, svid); + if (svid_data == NULL) + return false; + + if (svid_data->ops && svid_data->ops->ufp_request_exit_mode) + svid_data->ops->ufp_request_exit_mode(pd_port, svid_data, ops); + + return true; +} + +#endif /* PD_DPM_PRV_H_INCLUDED */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_dpm_reaction.c b/drivers/usb/typec/mmi_tcpc/pd_dpm_reaction.c new file mode 100644 index 000000000000..38809d2b89cf --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_dpm_reaction.c @@ -0,0 +1,770 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * PD Device Policy Manager Ready State reactions + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" +#include "inc/pd_dpm_core.h" +#include "inc/pd_dpm_pdo_select.h" +#include "pd_dpm_prv.h" + +#define DPM_REACTION_COND_ALWAYS (1<<0) +#define DPM_REACTION_COND_UFP_ONLY (1<<1) +#define DPM_REACTION_COND_DFP_ONLY (1<<2) +#define DPM_REACTION_COND_PD30 (1<<3) + +#define DPM_REACCOND_DFP \ + (DPM_REACTION_COND_ALWAYS | DPM_REACTION_COND_DFP_ONLY) + +#define DPM_REACCOND_UFP \ + (DPM_REACTION_COND_ALWAYS | DPM_REACTION_COND_UFP_ONLY) + +#define DPM_REACTION_COND_CHECK_ONCE (1<<5) +#define DPM_REACTION_COND_ONE_SHOT (1<<6) +#define DPM_REACTION_COND_LIMITED_RETRIES (1<<7) + +/* + * DPM flow delay reactions + */ + +#ifdef CONFIG_USB_PD_UFP_FLOW_DELAY +static uint8_t dpm_reaction_ufp_flow_delay(struct pd_port *pd_port) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_INFO("UFP Delay\n"); + pd_restart_timer(pd_port, PD_TIMER_UFP_FLOW_DELAY); + return DPM_READY_REACTION_BUSY; +} +#endif /* CONFIG_USB_PD_UFP_FLOW_DELAY */ + +#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY +static uint8_t dpm_reaction_dfp_flow_delay(struct pd_port *pd_port) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + DPM_INFO("DFP Delay\n"); + pd_restart_timer(pd_port, PD_TIMER_DFP_FLOW_DELAY); + return DPM_READY_REACTION_BUSY; +} +#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY */ + +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY +static uint8_t dpm_reaction_vconn_stable_delay(struct pd_port *pd_port) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->vconn_role == PD_ROLE_VCONN_DYNAMIC_ON) { + DPM_INFO("VStable Delay\n"); + return DPM_READY_REACTION_BUSY; + } + + return 0; +} +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + +/* + * DPM get cap reaction + */ + +#ifdef CONFIG_USB_PD_REV30 +static uint8_t dpm_reaction_get_source_cap_ext(struct pd_port *pd_port) +{ + if (pd_port->power_role == PD_ROLE_SINK) + return TCP_DPM_EVT_GET_SOURCE_CAP_EXT; + + return 0; +} +#endif /* CONFIG_USB_PD_REV30 */ + +static uint8_t dpm_reaction_get_sink_cap(struct pd_port *pd_port) +{ + return TCP_DPM_EVT_GET_SINK_CAP; +} + +static uint8_t dpm_reaction_get_source_cap(struct pd_port *pd_port) +{ + return TCP_DPM_EVT_GET_SOURCE_CAP; +} + +static uint8_t dpm_reaction_attemp_get_flag(struct pd_port *pd_port) +{ + return TCP_DPM_EVT_GET_SINK_CAP; +} + +/* + * DPM swap reaction + */ + +#ifdef CONFIG_USB_PD_PR_SWAP +static uint8_t dpm_reaction_request_pr_swap(struct pd_port *pd_port) +{ + uint32_t prefer_role = + DPM_CAP_EXTRACT_PR_CHECK(pd_port->dpm_caps); + + if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER)) + return 0; + + if (pd_port->power_role == PD_ROLE_SINK) { + if (prefer_role == DPM_CAP_PR_CHECK_PREFER_SRC) + return TCP_DPM_EVT_PR_SWAP_AS_SRC; + } else { +#ifdef CONFIG_USB_PD_SRC_TRY_PR_SWAP_IF_BAD_PW + if (dpm_check_good_power(pd_port) == GOOD_PW_PARTNER) + return TCP_DPM_EVT_PR_SWAP_AS_SNK; +#endif /* CONFIG_USB_PD_SRC_TRY_PR_SWAP_IF_BAD_PW */ + + if (prefer_role == DPM_CAP_PR_CHECK_PREFER_SNK) + return TCP_DPM_EVT_PR_SWAP_AS_SNK; + } + + return 0; +} +#endif /* CONFIG_USB_PD_PR_SWAP */ + +#ifdef CONFIG_USB_PD_DR_SWAP +static uint8_t dpm_reaction_request_dr_swap(struct pd_port *pd_port) +{ + uint32_t prefer_role = + DPM_CAP_EXTRACT_DR_CHECK(pd_port->dpm_caps); + + if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_DR_DATA)) + return 0; + + if (pd_port->data_role == PD_ROLE_DFP + && prefer_role == DPM_CAP_DR_CHECK_PREFER_UFP) + return TCP_DPM_EVT_DR_SWAP_AS_UFP; + + if (pd_port->data_role == PD_ROLE_UFP + && prefer_role == DPM_CAP_DR_CHECK_PREFER_DFP) + return TCP_DPM_EVT_DR_SWAP_AS_DFP; + + return 0; +} +#endif /* #ifdef CONFIG_USB_PD_DR_SWAP */ + + +/* + * DPM DiscoverCable reaction + */ + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE +static uint8_t dpm_reaction_dynamic_vconn(struct pd_port *pd_port) +{ + pd_dpm_dynamic_enable_vconn(pd_port); + return 0; +} +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + +#ifdef CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN +static uint8_t dpm_reaction_request_vconn_source(struct pd_port *pd_port) +{ + bool return_vconn = true; + + if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_VCONN_SUPPLY)) + return 0; + + if (pd_port->vconn_role) + return 0; + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + if (pd_port->tcpc->tcpc_vconn_supply == TCPC_VCONN_SUPPLY_STARTUP) + return_vconn = false; +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + + if (pd_check_rev30(pd_port)) + return_vconn = false; + + if (return_vconn) + dpm_reaction_set(pd_port, DPM_REACTION_RETURN_VCONN_SRC); + + return TCP_DPM_EVT_VCONN_SWAP_ON; +} +#endif /* CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN */ + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID +static uint8_t pd_dpm_reaction_discover_cable(struct pd_port *pd_port) +{ +#ifdef CONFIG_PD_DFP_RESET_CABLE + if (pd_is_reset_cable(pd_port)) + return TCP_DPM_EVT_CABLE_SOFTRESET; +#endif /* CONFIG_PD_DFP_RESET_CABLE */ + + if (pd_is_discover_cable(pd_port)) { + pd_restart_timer(pd_port, PD_TIMER_DISCOVER_ID); + return DPM_READY_REACTION_BUSY; + } + + return 0; +} +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN +static uint8_t dpm_reaction_return_vconn_source(struct pd_port *pd_port) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->vconn_role) { + DPM_DBG("VconnReturn\n"); + return TCP_DPM_EVT_VCONN_SWAP_OFF; + } + + return 0; +} +#endif /* CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN */ + +/* + * DPM EnterMode reaction + */ + +#ifdef CONFIG_USB_PD_ATTEMP_DISCOVER_ID +static uint8_t dpm_reaction_discover_id(struct pd_port *pd_port) +{ + return TCP_DPM_EVT_DISCOVER_ID; +} +#endif /* CONFIG_USB_PD_ATTEMP_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_ATTEMP_DISCOVER_SVID +static uint8_t dpm_reaction_discover_svid(struct pd_port *pd_port) +{ + return TCP_DPM_EVT_DISCOVER_SVIDS; +} +#endif /* CONFIG_USB_PD_ATTEMP_DISCOVER_SVID */ + +#ifdef CONFIG_USB_PD_MODE_OPERATION +static uint8_t dpm_reaction_mode_operation(struct pd_port *pd_port) +{ + if (svdm_notify_pe_ready(pd_port)) + return DPM_READY_REACTION_BUSY; + + return 0; +} +#endif /* CONFIG_USB_PD_MODE_OPERATION */ + +/* + * DPM Local/Remote Alert reaction + */ + +#ifdef CONFIG_USB_PD_REV30 + +#ifdef CONFIG_USB_PD_DPM_AUTO_SEND_ALERT + +static uint8_t dpm_reaction_send_alert(struct pd_port *pd_port) +{ + uint32_t alert_urgent; + struct pe_data *pe_data = &pd_port->pe_data; + + alert_urgent = pe_data->local_alert; + alert_urgent &= ~ADO_GET_STATUS_ONCE_MASK; + + if (!pe_data->get_status_once) + pe_data->local_alert = alert_urgent; + + if ((!pe_data->pe_ready) && (alert_urgent == 0)) + return 0; + + if (pe_data->local_alert == 0) + return 0; + + return TCP_DPM_EVT_ALERT; +} + +#endif /* CONFIG_USB_PD_DPM_AUTO_SEND_ALERT */ + +#ifdef CONFIG_USB_PD_DPM_AUTO_GET_STATUS + +const uint32_t c_get_status_alert_type = ADO_ALERT_OCP| + ADO_ALERT_OTP|ADO_ALERT_OVP|ADO_ALERT_OPER_CHANGED| + ADO_ALERT_SRC_IN_CHANGED; + +static inline uint8_t dpm_reaction_alert_status_changed(struct pd_port *pd_port) +{ + pd_port->pe_data.remote_alert &= + ~ADO_ALERT_TYPE_SET(c_get_status_alert_type); + + return TCP_DPM_EVT_GET_STATUS; +} + +static inline uint8_t dpm_reaction_alert_battry_changed(struct pd_port *pd_port) +{ + uint8_t i; + uint8_t mask; + uint8_t bat_change_i = 255; + uint8_t bat_change_mask1, bat_change_mask2; + + bat_change_mask1 = ADO_FIXED_BAT(pd_port->pe_data.remote_alert); + bat_change_mask2 = ADO_HOT_SWAP_BAT(pd_port->pe_data.remote_alert); + + if (bat_change_mask1) { + for (i = 0; i < 4; i++) { + mask = 1<pe_data.remote_alert &= + ~ADO_FIXED_BAT_SET(bat_change_mask1); + break; + } + } + } else if (bat_change_mask2) { + for (i = 0; i < 4; i++) { + mask = 1<pe_data.remote_alert &= + ~ADO_HOT_SWAP_BAT_SET(bat_change_mask2); + break; + } + } + } + + if (bat_change_mask1 == 0 && bat_change_mask2 == 0) { + pd_port->pe_data.remote_alert &= + ~ADO_ALERT_TYPE_SET(ADO_ALERT_BAT_CHANGED); + } + + if (bat_change_i == 255) + return 0; + + pd_port->tcp_event.tcp_dpm_data.data_object[0] = bat_change_i; + return TCP_DPM_EVT_GET_BAT_STATUS; +} + +static uint8_t dpm_reaction_handle_alert(struct pd_port *pd_port) +{ + uint32_t alert_type = ADO_ALERT_TYPE(pd_port->pe_data.remote_alert); + + if (alert_type & c_get_status_alert_type) + return dpm_reaction_alert_status_changed(pd_port); + + if (alert_type & ADO_ALERT_BAT_CHANGED) + return dpm_reaction_alert_battry_changed(pd_port); + + return 0; +} +#endif /* CONFIG_USB_PD_DPM_AUTO_GET_STATUS */ + +#endif /* CONFIG_USB_PD_REV30 */ + +/* + * DPM Idle reaction + */ + +static inline uint8_t dpm_get_pd_connect_state(struct pd_port *pd_port) +{ + if (pd_port->power_role == PD_ROLE_SOURCE) { + if (pd_check_rev30(pd_port)) + return PD_CONNECT_PE_READY_SRC_PD30; + + return PD_CONNECT_PE_READY_SRC; + } + + if (pd_check_rev30(pd_port)) { + if (pd_is_source_support_apdo(pd_port)) + return PD_CONNECT_PE_READY_SNK_APDO; + + return PD_CONNECT_PE_READY_SNK_PD30; + } + + return PD_CONNECT_PE_READY_SNK; +} + +static inline void dpm_check_vconn_highv_prot(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_VCONN_SAFE5V_ONLY + struct tcpc_device *tcpc = pd_port->tcpc; + struct pe_data *pe_data = &pd_port->pe_data; + bool vconn_highv_prot = pd_port->request_v_new > 5000; + + if (pe_data->vconn_highv_prot && !vconn_highv_prot && + tcpc->tcpc_flags & TCPC_FLAGS_VCONN_SAFE5V_ONLY) { + DPM_INFO("VC_HIGHV_PROT: %d\n", vconn_highv_prot); + pe_data->vconn_highv_prot = vconn_highv_prot; + pd_set_vconn(pd_port, pe_data->vconn_highv_prot_role); + } +#endif /* CONFIG_USB_PD_VCONN_SAFE5V_ONLY */ +} + +static uint8_t dpm_reaction_update_pe_ready(struct pd_port *pd_port) +{ + uint8_t state; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (!pd_port->pe_data.pe_ready) { + DPM_INFO("PE_READY\n"); + pd_port->pe_data.pe_ready = true; +#ifdef CONFIG_DUAL_ROLE_USB_INTF + dual_role_instance_changed(pd_port->tcpc->dr_usb); +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + } + + state = dpm_get_pd_connect_state(pd_port); + pd_update_connect_state(pd_port, state); + + dpm_check_vconn_highv_prot(pd_port); + pd_dpm_dynamic_disable_vconn(pd_port); + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + pd_port->pe_data.pd_traffic_idle = true; + if (pd_check_rev30(pd_port) && + (pd_port->power_role == PD_ROLE_SOURCE)) + pd_set_sink_tx(pd_port, PD30_SINK_TX_OK); +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + + return 0; +} + +/* + * DPM reaction declaration + */ + +typedef uint8_t (*dpm_reaction_fun)(struct pd_port *pd_port); + +struct dpm_ready_reaction { + uint32_t bit_mask; + uint8_t condition; + dpm_reaction_fun handler; +}; + +#define DECL_DPM_REACTION(xmask, xcond, xhandler) { \ + .bit_mask = xmask, \ + .condition = xcond, \ + .handler = xhandler, \ +} + +#define DECL_DPM_REACTION_ALWAYS(xhandler) \ + DECL_DPM_REACTION(DPM_REACTION_CAP_ALWAYS, \ + DPM_REACTION_COND_ALWAYS, \ + xhandler) + +#define DECL_DPM_REACTION_CHECK_ONCE(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_ALWAYS | \ + DPM_REACTION_COND_CHECK_ONCE, \ + xhandler) + +#define DECL_DPM_REACTION_LIMITED_RETRIES(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_ALWAYS |\ + DPM_REACTION_COND_LIMITED_RETRIES, \ + xhandler) + +#define DECL_DPM_REACTION_ONE_SHOT(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_ALWAYS | \ + DPM_REACTION_COND_ONE_SHOT, \ + xhandler) + +#define DECL_DPM_REACTION_UFP(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_UFP_ONLY, \ + xhandler) + +#define DECL_DPM_REACTION_DFP(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_DFP_ONLY, \ + xhandler) + +#define DECL_DPM_REACTION_PD30(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_PD30, \ + xhandler) + +#define DECL_DPM_REACTION_PD30_LIMITED_RETRIES(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_PD30 |\ + DPM_REACTION_COND_LIMITED_RETRIES, \ + xhandler) + +#define DECL_DPM_REACTION_PD30_ONE_SHOT(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_PD30 | \ + DPM_REACTION_COND_ONE_SHOT, \ + xhandler) + +#define DECL_DPM_REACTION_DFP_PD30_ONE_SHOT(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_DFP_ONLY |\ + DPM_REACTION_COND_PD30 | \ + DPM_REACTION_COND_ONE_SHOT, \ + xhandler) + +#define DECL_DPM_REACTION_DFP_PD30_LIMITED_RETRIES(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_DFP_ONLY |\ + DPM_REACTION_COND_PD30 | \ + DPM_REACTION_COND_LIMITED_RETRIES, \ + xhandler) + +#define DECL_DPM_REACTION_DFP_PD30_CHECK_ONCE(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_DFP_ONLY |\ + DPM_REACTION_COND_PD30 | \ + DPM_REACTION_COND_CHECK_ONCE, \ + xhandler) + +#define DECL_DPM_REACTION_DFP_PD30_RUN_ONCE(xmask, xhandler) \ + DECL_DPM_REACTION(xmask, \ + DPM_REACTION_COND_DFP_ONLY |\ + DPM_REACTION_COND_PD30 | \ + DPM_REACTION_COND_CHECK_ONCE | \ + DPM_REACTION_COND_ONE_SHOT, \ + xhandler) + +static const struct dpm_ready_reaction dpm_reactions[] = { + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_DPM_AUTO_SEND_ALERT + DECL_DPM_REACTION_PD30( + DPM_REACTION_CAP_ALWAYS, + dpm_reaction_send_alert), +#endif /* CONFIG_USB_PD_DPM_AUTO_SEND_ALERT */ +#ifdef CONFIG_USB_PD_DPM_AUTO_GET_STATUS + DECL_DPM_REACTION_PD30( + DPM_REACTION_CAP_ALWAYS, + dpm_reaction_handle_alert), +#endif /* CONFIG_USB_PD_DPM_AUTO_GET_STATUS */ +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY + DECL_DPM_REACTION_DFP( + DPM_REACTION_DFP_FLOW_DELAY, + dpm_reaction_dfp_flow_delay), +#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY */ + +#ifdef CONFIG_USB_PD_UFP_FLOW_DELAY + DECL_DPM_REACTION_UFP( + DPM_REACTION_UFP_FLOW_DELAY, + dpm_reaction_ufp_flow_delay), +#endif /* CONFIG_USB_PD_UFP_FLOW_DELAY */ + + DECL_DPM_REACTION_LIMITED_RETRIES( + DPM_REACTION_GET_SINK_CAP, + dpm_reaction_get_sink_cap), + + DECL_DPM_REACTION_LIMITED_RETRIES( + DPM_REACTION_GET_SOURCE_CAP, + dpm_reaction_get_source_cap), + + DECL_DPM_REACTION_LIMITED_RETRIES( + DPM_REACTION_ATTEMPT_GET_FLAG, + dpm_reaction_attemp_get_flag), + +#ifdef CONFIG_USB_PD_REV30 + DECL_DPM_REACTION_PD30_ONE_SHOT( + DPM_REACTION_GET_SOURCE_CAP_EXT, + dpm_reaction_get_source_cap_ext), +#endif /* CONFIG_USB_PD_REV30 */ + +#ifdef CONFIG_USB_PD_PR_SWAP + DECL_DPM_REACTION_CHECK_ONCE( + DPM_REACTION_REQUEST_PR_SWAP, + dpm_reaction_request_pr_swap), +#endif /* CONFIG_USB_PD_PR_SWAP */ + +#ifdef CONFIG_USB_PD_DR_SWAP + DECL_DPM_REACTION_CHECK_ONCE( + DPM_REACTION_REQUEST_DR_SWAP, + dpm_reaction_request_dr_swap), +#endif /* CONFIG_USB_PD_DR_SWAP */ + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + DECL_DPM_REACTION_DFP_PD30_CHECK_ONCE( + DPM_REACTION_DYNAMIC_VCONN, + dpm_reaction_dynamic_vconn), +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + +#ifdef CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN + DECL_DPM_REACTION_DFP_PD30_RUN_ONCE( + DPM_REACTION_REQUEST_VCONN_SRC, + dpm_reaction_request_vconn_source), +#endif /* CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN */ + +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY + DECL_DPM_REACTION_DFP_PD30_CHECK_ONCE( + DPM_REACTION_VCONN_STABLE_DELAY, + dpm_reaction_vconn_stable_delay), +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID + DECL_DPM_REACTION_DFP_PD30_CHECK_ONCE( + DPM_REACTION_DISCOVER_CABLE, + pd_dpm_reaction_discover_cable), +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN + DECL_DPM_REACTION_DFP_PD30_RUN_ONCE( + DPM_REACTION_RETURN_VCONN_SRC, + dpm_reaction_return_vconn_source), +#endif /* CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN */ + +#ifdef CONFIG_USB_PD_ATTEMP_DISCOVER_ID + DECL_DPM_REACTION_DFP_PD30_LIMITED_RETRIES( + DPM_REACTION_DISCOVER_ID, + dpm_reaction_discover_id), +#endif /* CONFIG_USB_PD_ATTEMP_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_ATTEMP_DISCOVER_SVID + DECL_DPM_REACTION_DFP_PD30_LIMITED_RETRIES( + DPM_REACTION_DISCOVER_SVID, + dpm_reaction_discover_svid), +#endif /* CONFIG_USB_PD_ATTEMP_DISCOVER_SVID */ + +#ifdef CONFIG_USB_PD_MODE_OPERATION + DECL_DPM_REACTION_ALWAYS( + dpm_reaction_mode_operation), +#endif /* CONFIG_USB_PD_MODE_OPERATION */ + + DECL_DPM_REACTION_ALWAYS( + dpm_reaction_update_pe_ready), +}; + +/** + * dpm_get_reaction_env + * + * Get current reaction's environmental conditions. + * + * Returns environmental conditions. + */ + +static inline uint8_t dpm_get_reaction_env(struct pd_port *pd_port) +{ + uint8_t conditions; + + if (pd_port->data_role == PD_ROLE_DFP) + conditions = DPM_REACCOND_DFP; + else + conditions = DPM_REACCOND_UFP; + + if (pd_check_rev30(pd_port)) + conditions |= DPM_REACTION_COND_PD30; + + return conditions; +} + +/** + * dpm_check_reaction_busy + * + * check this reaction is still keep busy + * + * @ reaction : which reaction is checked. + * + * Return Boolean to indicate busy or not. + */ + +static inline bool dpm_check_reaction_busy(struct pd_port *pd_port, + const struct dpm_ready_reaction *reaction) +{ + if (reaction->bit_mask & DPM_REACTION_CAP_ALWAYS) + return false; + + return !dpm_reaction_check( + pd_port, DPM_REACTION_CAP_READY_ONCE); +} + +/** + * dpm_check_reaction_available + * + * check this reaction is available. + * + * @ env : Environmental conditions of reaction table + * @ reaction : which reaction is checked. + * + * Returns TCP_DPM_EVT_ID if available; + * Returns Zero to indicate if not available. + * Returns DPM_READY_REACTION_BUSY to indicate + * this reaction is waiting for being finished. + */ + +static inline uint8_t dpm_check_reaction_available(struct pd_port *pd_port, + uint8_t env, const struct dpm_ready_reaction *reaction) +{ + int ret; + + if (!dpm_reaction_check(pd_port, reaction->bit_mask)) + return 0; + + if (!(reaction->condition & env)) + return 0; + + if (dpm_check_reaction_busy(pd_port, reaction)) + return DPM_READY_REACTION_BUSY; + + ret = reaction->handler(pd_port); + + if (ret == 0 && + reaction->condition & DPM_REACTION_COND_CHECK_ONCE) + dpm_reaction_clear(pd_port, reaction->bit_mask); + + return ret; +} + +/** + * dpm_check_reset_reaction + * + * Once trigger one reaction, + * check if automatically clear this reaction flag. + * + * For the following reactions type: + * DPM_REACTION_COND_ONE_SHOT, + * DPM_REACTION_COND_LIMITED_RETRIES + * + * @ reaction : which reaction is triggered. + * + * Return Boolean to indicate automatically clear or not. + */ + +static inline bool dpm_check_clear_reaction(struct pd_port *pd_port, + const struct dpm_ready_reaction *reaction) +{ + if (pd_port->pe_data.dpm_reaction_id != reaction->bit_mask) + pd_port->pe_data.dpm_reaction_retry = 0; + + pd_port->pe_data.dpm_reaction_retry++; + pd_port->pe_data.dpm_reaction_id = reaction->bit_mask; + + if (reaction->condition & DPM_REACTION_COND_ONE_SHOT) + return true; + + if (reaction->condition & DPM_REACTION_COND_LIMITED_RETRIES) + return pd_port->pe_data.dpm_reaction_retry > 3; + + return false; +} + +uint8_t pd_dpm_get_ready_reaction(struct pd_port *pd_port) +{ + uint8_t evt; + uint8_t env; + uint32_t clear_reaction = DPM_REACTION_CAP_READY_ONCE; + const struct dpm_ready_reaction *reaction = dpm_reactions; + const struct dpm_ready_reaction *reaction_last = + dpm_reactions + ARRAY_SIZE(dpm_reactions); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + env = dpm_get_reaction_env(pd_port); + + do { + evt = dpm_check_reaction_available(pd_port, env, reaction); + } while ((evt == 0) && (++reaction < reaction_last)); + + if (evt > 0 && dpm_check_clear_reaction(pd_port, reaction)) { + clear_reaction |= reaction->bit_mask; + DPM_DBG("clear_reaction=%d\n", evt); + } + + dpm_reaction_clear(pd_port, clear_reaction); + return evt; +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_dpm_uvdm.c b/drivers/usb/typec/mmi_tcpc/pd_dpm_uvdm.c new file mode 100644 index 000000000000..2a47c3ffc195 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_dpm_uvdm.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * PD Device Policy Manager for UVDM + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" +#include "inc/pd_dpm_core.h" +#include "pd_dpm_prv.h" + +#ifdef CONFIG_USB_PD_RICHTEK_UVDM + +bool richtek_dfp_notify_pe_startup( + struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + UVDM_INFO("%s\n", __func__); + pd_port->richtek_init_done = false; + return true; +} + +int richtek_dfp_notify_pe_ready( + struct pd_port *pd_port, struct svdm_svid_data *svid_data) +{ + if (pd_port->data_role != PD_ROLE_DFP) + return 0; + + if (pd_port->richtek_init_done) + return 0; + + pd_port->richtek_init_done = true; + UVDM_INFO("%s\n", __func__); + +#if 0 + pd_port->uvdm_cnt = 3; + pd_port->uvdm_wait_resp = true; + + pd_port->uvdm_data[0] = PD_UVDM_HDR(USB_VID_RICHTEK, 0x4321); + pd_port->uvdm_data[1] = 0x11223344; + pd_port->uvdm_data[2] = 0x44332211; + + pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_UVDM); +#endif + + return 1; +} + +bool richtek_dfp_notify_uvdm(struct pd_port *pd_port, + struct svdm_svid_data *svid_data, bool ack) +{ + uint32_t resp_cmd = 0; + + if (ack) { + if (pd_port->uvdm_wait_resp) + resp_cmd = PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]); + + UVDM_INFO("dfp_notify: ACK (0x%x)\n", resp_cmd); + } else + UVDM_INFO("dfp_notify: NAK\n"); + + return true; +} + +bool richtek_ufp_notify_uvdm(struct pd_port *pd_port, + struct svdm_svid_data *svid_data) +{ + uint8_t i; + uint32_t reply_cmd[VDO_MAX_NR]; + uint16_t cmd = (uint16_t) PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]); + + UVDM_INFO("ufp_notify: 0x%x\n", cmd); + + if (cmd >= 0x1000) { + UVDM_INFO("uvdm_no_reply\n"); + VDM_STATE_DPM_INFORMED(pd_port); + return true; + } + + reply_cmd[0] = PD_UVDM_HDR(USB_VID_RICHTEK, cmd+1); + + for (i = 1; i < pd_port->uvdm_cnt; i++) + reply_cmd[i] = ~pd_port->uvdm_data[i]; + + pd_reply_custom_vdm(pd_port, TCPC_TX_SOP, + pd_port->uvdm_cnt, reply_cmd); + VDM_STATE_NORESP_CMD(pd_port); + return true; +} + +#endif /* CONFIG_USB_PD_RICHTEK_UVDM */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine.c new file mode 100644 index 000000000000..da0b3ed385ce --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine.c @@ -0,0 +1,1361 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine Driver + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_process_evt.h" +#include "inc/pd_policy_engine.h" + +/* ---- Policy Engine State ---- */ + +#if PE_DBG_ENABLE | PE_STATE_INFO_ENABLE +#if PE_STATE_FULL_NAME + +static const char *const pe_state_name[] = { + +/******************* Source *******************/ +#ifdef CONFIG_USB_PD_PE_SOURCE + "PE_SRC_STARTUP", + "PE_SRC_DISCOVERY", + "PE_SRC_SEND_CAPABILITIES", + "PE_SRC_NEGOTIATE_CAPABILITIES", + "PE_SRC_TRANSITION_SUPPLY", + "PE_SRC_TRANSITION_SUPPLY2", + "PE_SRC_READY", + "PE_SRC_DISABLED", + "PE_SRC_CAPABILITY_RESPONSE", + "PE_SRC_HARD_RESET", + "PE_SRC_HARD_RESET_RECEIVED", + "PE_SRC_TRANSITION_TO_DEFAULT", + "PE_SRC_GET_SINK_CAP", + "PE_SRC_WAIT_NEW_CAPABILITIES", + "PE_SRC_SEND_SOFT_RESET", + "PE_SRC_SOFT_RESET", +/* Source Startup Discover Cable */ +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#ifdef CONFIG_PD_SRC_RESET_CABLE + "PE_SRC_CBL_SEND_SOFT_RESET", +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + "PE_SRC_VDM_IDENTITY_REQUEST", + "PE_SRC_VDM_IDENTITY_ACKED", + "PE_SRC_VDM_IDENTITY_NAKED", +#endif /* PD_CAP_PE_SRC_STARTUP_DISCOVER_ID */ +/* Source for PD30 */ +#ifdef CONFIG_USB_PD_REV30 + "PE_SRC_SEND_NOT_SUPPORTED", + "PE_SRC_NOT_SUPPORTED_RECEIVED", + "PE_SRC_CHUNK_RECEIVED", +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + "PE_SRC_SEND_SOURCE_ALERT", +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + "PE_SRC_SINK_ALERT_RECEIVED", +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + "PE_SRC_GIVE_SOURCE_CAP_EXT", +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + "PE_SRC_GIVE_SOURCE_STATUS", +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + "PE_SRC_GET_SINK_STATUS", +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + "PE_SRC_GIVE_PPS_STATUS", +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SOURCE */ +/******************* Sink *******************/ +#ifdef CONFIG_USB_PD_PE_SINK + "PE_SNK_STARTUP", + "PE_SNK_DISCOVERY", + "PE_SNK_WAIT_FOR_CAPABILITIES", + "PE_SNK_EVALUATE_CAPABILITY", + "PE_SNK_SELECT_CAPABILITY", + "PE_SNK_TRANSITION_SINK", + "PE_SNK_READY", + "PE_SNK_HARD_RESET", + "PE_SNK_TRANSITION_TO_DEFAULT", + "PE_SNK_GIVE_SINK_CAP", + "PE_SNK_GET_SOURCE_CAP", + "PE_SNK_SEND_SOFT_RESET", + "PE_SNK_SOFT_RESET", +/* Sink for PD30 */ +#ifdef CONFIG_USB_PD_REV30 + "PE_SNK_SEND_NOT_SUPPORTED", + "PE_SNK_NOT_SUPPORTED_RECEIVED", + "PE_SNK_CHUNK_RECEIVED", +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + "PE_SNK_SOURCE_ALERT_RECEIVED", +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + "PE_SNK_SEND_SINK_ALERT", +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + "PE_SNK_GET_SOURCE_CAP_EXT", +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + "PE_SNK_GET_SOURCE_STATUS", +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + "PE_SNK_GIVE_SINK_STATUS", +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + "PE_SNK_GET_PPS_STATUS", +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SINK */ +/******************* DR_SWAP *******************/ +#ifdef CONFIG_USB_PD_DR_SWAP +/* DR_SWAP_DFP */ + "PE_DRS_DFP_UFP_EVALUATE_DR_SWAP", + "PE_DRS_DFP_UFP_ACCEPT_DR_SWAP", + "PE_DRS_DFP_UFP_CHANGE_TO_UFP", + "PE_DRS_DFP_UFP_SEND_DR_SWAP", + "PE_DRS_DFP_UFP_REJECT_DR_SWAP", +/* DR_SWAP_UFP */ + "PE_DRS_UFP_DFP_EVALUATE_DR_SWAP", + "PE_DRS_UFP_DFP_ACCEPT_DR_SWAP", + "PE_DRS_UFP_DFP_CHANGE_TO_DFP", + "PE_DRS_UFP_DFP_SEND_DR_SWAP", + "PE_DRS_UFP_DFP_REJECT_DR_SWAP", +#endif /* CONFIG_USB_PD_DR_SWAP */ +/******************* PR_SWAP *******************/ +#ifdef CONFIG_USB_PD_PR_SWAP +/* PR_SWAP_SRC */ + "PE_PRS_SRC_SNK_EVALUATE_PR_SWAP", + "PE_PRS_SRC_SNK_ACCEPT_PR_SWAP", + "PE_PRS_SRC_SNK_TRANSITION_TO_OFF", + "PE_PRS_SRC_SNK_ASSERT_RD", + "PE_PRS_SRC_SNK_WAIT_SOURCE_ON", + "PE_PRS_SRC_SNK_SEND_SWAP", + "PE_PRS_SRC_SNK_REJECT_PR_SWAP", +/* PR_SWAP_SNK */ + "PE_PRS_SNK_SRC_EVALUATE_PR_SWAP", + "PE_PRS_SNK_SRC_ACCEPT_PR_SWAP", + "PE_PRS_SNK_SRC_TRANSITION_TO_OFF", + "PE_PRS_SNK_SRC_ASSERT_RP", + "PE_PRS_SNK_SRC_SOURCE_ON", + "PE_PRS_SNK_SRC_SEND_SWAP", + "PE_PRS_SNK_SRC_REJECT_SWAP", +/* get same role cap */ + "PE_DR_SRC_GET_SOURCE_CAP", + "PE_DR_SRC_GIVE_SINK_CAP", + "PE_DR_SNK_GET_SINK_CAP", + "PE_DR_SNK_GIVE_SOURCE_CAP", +/* get same role cap for PD30 */ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + "PE_DR_SNK_GIVE_SOURCE_CAP_EXT", +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + "PE_DR_SRC_GET_SOURCE_CAP_EXT", +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PR_SWAP */ +/******************* VCONN_SWAP *******************/ +#ifdef CONFIG_USB_PD_VCONN_SWAP + "PE_VCS_SEND_SWAP", + "PE_VCS_EVALUATE_SWAP", + "PE_VCS_ACCEPT_SWAP", + "PE_VCS_REJECT_VCONN_SWAP", + "PE_VCS_WAIT_FOR_VCONN", + "PE_VCS_TURN_OFF_VCONN", + "PE_VCS_TURN_ON_VCONN", + "PE_VCS_SEND_PS_RDY", +#endif /* CONFIG_USB_PD_VCONN_SWAP */ +/******************* UFP_VDM *******************/ + "PE_UFP_VDM_GET_IDENTITY", + "PE_UFP_VDM_GET_SVIDS", + "PE_UFP_VDM_GET_MODES", + "PE_UFP_VDM_EVALUATE_MODE_ENTRY", + "PE_UFP_VDM_MODE_EXIT", + "PE_UFP_VDM_ATTENTION_REQUEST", +#ifdef CONFIG_USB_PD_ALT_MODE + "PE_UFP_VDM_DP_STATUS_UPDATE", + "PE_UFP_VDM_DP_CONFIGURE", +#endif/* CONFIG_USB_PD_ALT_MODE */ +/******************* DFP_VDM *******************/ + "PE_DFP_UFP_VDM_IDENTITY_REQUEST", + "PE_DFP_UFP_VDM_IDENTITY_ACKED", + "PE_DFP_UFP_VDM_IDENTITY_NAKED", + "PE_DFP_CBL_VDM_IDENTITY_REQUEST", + "PE_DFP_CBL_VDM_IDENTITY_ACKED", + "PE_DFP_CBL_VDM_IDENTITY_NAKED", + "PE_DFP_VDM_SVIDS_REQUEST", + "PE_DFP_VDM_SVIDS_ACKED", + "PE_DFP_VDM_SVIDS_NAKED", + "PE_DFP_VDM_MODES_REQUEST", + "PE_DFP_VDM_MODES_ACKED", + "PE_DFP_VDM_MODES_NAKED", + "PE_DFP_VDM_MODE_ENTRY_REQUEST", + "PE_DFP_VDM_MODE_ENTRY_ACKED", + "PE_DFP_VDM_MODE_ENTRY_NAKED", + "PE_DFP_VDM_MODE_EXIT_REQUEST", + "PE_DFP_VDM_MODE_EXIT_ACKED", + "PE_DFP_VDM_ATTENTION_REQUEST", +#ifdef CONFIG_PD_DFP_RESET_CABLE + "PE_DFP_CBL_SEND_SOFT_RESET", + "PE_DFP_CBL_SEND_CABLE_RESET", +#endif /* CONFIG_PD_DFP_RESET_CABLE */ +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + "PE_DFP_VDM_DP_STATUS_UPDATE_REQUEST", + "PE_DFP_VDM_DP_STATUS_UPDATE_ACKED", + "PE_DFP_VDM_DP_STATUS_UPDATE_NAKED", + "PE_DFP_VDM_DP_CONFIGURATION_REQUEST", + "PE_DFP_VDM_DP_CONFIGURATION_ACKED", + "PE_DFP_VDM_DP_CONFIGURATION_NAKED", +#endif/* CONFIG_USB_PD_ALT_MODE_DFP */ +/******************* UVDM & SVDM *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_VDM + "PE_UFP_UVDM_RECV", + "PE_DFP_UVDM_SEND", + "PE_DFP_UVDM_ACKED", + "PE_DFP_UVDM_NAKED", +#endif/* CONFIG_USB_PD_CUSTOM_VDM */ +/******************* PD30 Common *******************/ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE + "PE_GET_BATTERY_CAP", +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL + "PE_GIVE_BATTERY_CAP", +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE + "PE_GET_BATTERY_STATUS", +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL + "PE_GIVE_BATTERY_STATUS", +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + "PE_GET_MANUFACTURER_INFO", +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + "PE_GIVE_MANUFACTURER_INFO", +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE + "PE_GET_COUNTRY_CODES", +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL + "PE_GIVE_COUNTRY_CODES", +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE + "PE_GET_COUNTRY_INFO", +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL + "PE_GIVE_COUNTRY_INFO", +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ + + "PE_VDM_NOT_SUPPORTED", +#endif /* CONFIG_USB_PD_REV30 */ +/******************* Others *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + "PE_DBG_READY", +#endif/* CONFIG_USB_PD_CUSTOM_DBGACC */ +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER + "PE_OVER_RECV_HRESET_LIMIT", +#endif/* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + "PE_REJECT", + "PE_ERROR_RECOVERY", +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE + "PE_ERROR_RECOVERY_ONCE", +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + "PE_BIST_TEST_DATA", + "PE_BIST_CARRIER_MODE_2", +/* Wait tx finished */ + "PE_IDLE1", + "PE_IDLE2", +}; + +#else + +static const char *const pe_state_name[] = { +/******************* Source *******************/ +#ifdef CONFIG_USB_PD_PE_SOURCE + "SRC_START", + "SRC_DISC", + "SRC_SEND_CAP", + "SRC_NEG_CAP", + "SRC_TRANS_SUPPLY", + "SRC_TRANS_SUPPLY2", + "SRC_READY", + "SRC_DISABLED", + "SRC_CAP_RESP", + "SRC_HRESET", + "SRC_HRESET_RECV", + "SRC_TRANS_DFT", + "SRC_GET_CAP", + "SRC_WAIT_CAP", + "SRC_SEND_SRESET", + "SRC_SRESET", +/* Source Startup Discover Cable */ +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#ifdef CONFIG_PD_SRC_RESET_CABLE + "SRC_CBL_SEND_SRESET", +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + "SRC_VDM_ID_REQ", + "SRC_VDM_ID_ACK", + "SRC_VDM_ID_NAK", +#endif /* PD_CAP_PE_SRC_STARTUP_DISCOVER_ID */ +/* Source for PD30 */ +#ifdef CONFIG_USB_PD_REV30 + "SRC_NO_SUPP", + "SRC_NO_SUPP_RECV", + "SRC_CK_RECV", +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + "SRC_ALERT", +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + "SRC_RECV_ALERT", +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + "SRC_GIVE_CAP_EXT", +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + "SRC_GIVE_STATUS", +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + "SRC_GET_STATUS", +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + "SRC_GIVE_PPS", +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SOURCE */ +/******************* Sink *******************/ +#ifdef CONFIG_USB_PD_PE_SINK +/* Sink Init */ + "SNK_START", + "SNK_DISC", + "SNK_WAIT_CAP", + "SNK_EVA_CAP", + "SNK_SEL_CAP", + "SNK_TRANS_SINK", + "SNK_READY", + "SNK_HRESET", + "SNK_TRANS_DFT", + "SNK_GIVE_CAP", + "SNK_GET_CAP", + "SNK_SEND_SRESET", + "SNK_SRESET", +/* Sink for PD30 */ +#ifdef CONFIG_USB_PD_REV30 + "SNK_NO_SUPP", + "SNK_NO_SUPP_RECV", + "SNK_CK_RECV", +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + "SNK_RECV_ALERT", +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + "SNK_ALERT", +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + "SNK_GET_CAP_EX", +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + "SNK_GET_STATUS", +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + "SNK_GIVE_STATUS", +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + "SNK_GET_PPS", +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SINK */ +/******************* DR_SWAP *******************/ +#ifdef CONFIG_USB_PD_DR_SWAP +/* DR_SWAP_DFP */ + "D_DFP_EVA", + "D_DFP_ACCEPT", + "D_DFP_CHANGE", + "D_DFP_SEND", + "D_DFP_REJECT", +/* DR_SWAP_UFP */ + "D_UFP_EVA", + "D_UFP_ACCEPT", + "D_UFP_CHANGE", + "D_UFP_SEND", + "D_UFP_REJECT", +#endif /* CONFIG_USB_PD_DR_SWAP */ +/******************* PR_SWAP *******************/ +#ifdef CONFIG_USB_PD_PR_SWAP +/* PR_SWAP_SRC */ + "P_SRC_EVA", + "P_SRC_ACCEPT", + "P_SRC_TRANS_OFF", + "P_SRC_ASSERT", + "P_SRC_WAIT_ON", + "P_SRC_SEND", + "P_SRC_REJECT", + +/* PR_SWAP_SNK */ + "P_SNK_EVA", + "P_SNK_ACCEPT", + "P_SNK_TRANS_OFF", + "P_SNK_ASSERT", + "P_SNK_SOURCE_ON", + "P_SNK_SEND", + "P_SNK_REJECT", +/* get same role cap */ + "DR_SRC_GET_CAP", + "DR_SRC_GIVE_CAP", + "DR_SNK_GET_CAP", + "DR_SNK_GIVE_CAP", +/* get same role cap for PD30 */ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + "DR_SNK_GIVE_CAP_EXT", +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + "DR_SRC_GET_CAP_EXT", +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PR_SWAP */ +/******************* VCONN_SWAP *******************/ +#ifdef CONFIG_USB_PD_VCONN_SWAP + "V_SEND", + "V_EVA", + "V_ACCEPT", + "V_REJECT", + "V_WAIT_VCONN", + "V_TURN_OFF", + "V_TURN_ON", + "V_PS_RDY", +#endif /* CONFIG_USB_PD_VCONN_SWAP */ +/******************* UFP_VDM *******************/ + "U_GET_ID", + "U_GET_SVID", + "U_GET_MODE", + "U_EVA_MODE", + "U_MODE_EX", + "U_ATTENTION", +#ifdef CONFIG_USB_PD_ALT_MODE + "U_D_STATUS", + "U_D_CONFIG", +#endif/* CONFIG_USB_PD_ALT_MODE */ +/******************* DFP_VDM *******************/ + "D_UID_REQ", + "D_UID_A", + "D_UID_N", + "D_CID_REQ", + "D_CID_ACK", + "D_CID_NAK", + "D_SVID_REQ", + "D_SVID_ACK", + "D_SVID_NAK", + "D_MODE_REQ", + "D_MODE_ACK", + "D_MODE_NAK", + "D_MODE_EN_REQ", + "D_MODE_EN_ACK", + "D_MODE_EN_NAK", + "D_MODE_EX_REQ", + "D_MODE_EX_ACK", + "D_ATTENTION", +#ifdef CONFIG_PD_DFP_RESET_CABLE + "D_C_SRESET", + "D_C_CRESET", +#endif /* CONFIG_PD_DFP_RESET_CABLE */ +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + "D_DP_STATUS_REQ", + "D_DP_STATUS_ACK", + "D_DP_STATUS_NAK", + "D_DP_CONFIG_REQ", + "D_DP_CONFIG_ACK", + "D_DP_CONFIG_NAK", +#endif/* CONFIG_USB_PD_ALT_MODE_DFP */ +/******************* UVDM & SVDM *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_VDM + "U_UVDM_RECV", + "D_UVDM_SEND", + "D_UVDM_ACKED", + "D_UVDM_NAKED", +#endif/* CONFIG_USB_PD_CUSTOM_VDM */ +/******************* PD30 Common *******************/ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE + "GET_BAT_CAP", +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL + "GIVE_BAT_CAP", +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE + "GET_BAT_STATUS", +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL + "GIVE_BAT_STATUS", +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + "GET_MFRS_INFO", +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + "GIVE_MFRS_INFO", +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE + "GET_CC", +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL + "GIVE_CC", +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE + "GET_CI", +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL + "GIVE_CI", +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ + + "VDM_NO_SUPP", +#endif /* CONFIG_USB_PD_REV30 */ +/******************* Others *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + "DBG_READY", +#endif/* CONFIG_USB_PD_CUSTOM_DBGACC */ +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER + "OVER_HRESET_LIMIT", +#endif/* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + "REJECT", + "ERR_RECOVERY", +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE + "ERR_RECOVERY1", +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + "BIST_TD", + "BIST_C2", +/* Wait tx finished */ + "IDLE1", + "IDLE2", +}; +#endif /* PE_STATE_FULL_NAME */ +#endif /* PE_DBG_ENABLE | PE_STATE_INFO_ENABLE */ + +struct pe_state_actions { + void (*entry_action) + (struct pd_port *pd_port); + /* const void (*exit_action) + * (struct pd_port *pd_port, struct pd_event *pd_event); + */ +}; + +#define PE_STATE_ACTIONS(state) { .entry_action = state##_entry, } + +static const struct pe_state_actions pe_state_actions[] = { +/******************* Source *******************/ +#ifdef CONFIG_USB_PD_PE_SOURCE + PE_STATE_ACTIONS(pe_src_startup), + PE_STATE_ACTIONS(pe_src_discovery), + PE_STATE_ACTIONS(pe_src_send_capabilities), + PE_STATE_ACTIONS(pe_src_negotiate_capabilities), + PE_STATE_ACTIONS(pe_src_transition_supply), + PE_STATE_ACTIONS(pe_src_transition_supply2), + PE_STATE_ACTIONS(pe_src_ready), + PE_STATE_ACTIONS(pe_src_disabled), + PE_STATE_ACTIONS(pe_src_capability_response), + PE_STATE_ACTIONS(pe_src_hard_reset), + PE_STATE_ACTIONS(pe_src_hard_reset_received), + PE_STATE_ACTIONS(pe_src_transition_to_default), + PE_STATE_ACTIONS(pe_src_get_sink_cap), + PE_STATE_ACTIONS(pe_src_wait_new_capabilities), + PE_STATE_ACTIONS(pe_src_send_soft_reset), + PE_STATE_ACTIONS(pe_src_soft_reset), +/* Source Startup Discover Cable */ +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID +#ifdef CONFIG_PD_SRC_RESET_CABLE + PE_STATE_ACTIONS(pe_src_cbl_send_soft_reset), +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + PE_STATE_ACTIONS(pe_src_vdm_identity_request), + PE_STATE_ACTIONS(pe_src_vdm_identity_acked), + PE_STATE_ACTIONS(pe_src_vdm_identity_naked), +#endif /* PD_CAP_PE_SRC_STARTUP_DISCOVER_ID */ +/* Source for PD30 */ +#ifdef CONFIG_USB_PD_REV30 + PE_STATE_ACTIONS(pe_src_send_not_supported), + PE_STATE_ACTIONS(pe_src_not_supported_received), + PE_STATE_ACTIONS(pe_src_chunk_received), +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + PE_STATE_ACTIONS(pe_src_send_source_alert), +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + PE_STATE_ACTIONS(pe_src_sink_alert_received), +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + PE_STATE_ACTIONS(pe_src_give_source_cap_ext), +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + PE_STATE_ACTIONS(pe_src_give_source_status), +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + PE_STATE_ACTIONS(pe_src_get_sink_status), +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + PE_STATE_ACTIONS(pe_src_give_pps_status), +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SOURCE */ +/******************* Sink *******************/ +#ifdef CONFIG_USB_PD_PE_SINK +/* Sink Init */ + PE_STATE_ACTIONS(pe_snk_startup), + PE_STATE_ACTIONS(pe_snk_discovery), + PE_STATE_ACTIONS(pe_snk_wait_for_capabilities), + PE_STATE_ACTIONS(pe_snk_evaluate_capability), + PE_STATE_ACTIONS(pe_snk_select_capability), + PE_STATE_ACTIONS(pe_snk_transition_sink), + PE_STATE_ACTIONS(pe_snk_ready), + PE_STATE_ACTIONS(pe_snk_hard_reset), + PE_STATE_ACTIONS(pe_snk_transition_to_default), + PE_STATE_ACTIONS(pe_snk_give_sink_cap), + PE_STATE_ACTIONS(pe_snk_get_source_cap), + PE_STATE_ACTIONS(pe_snk_send_soft_reset), + PE_STATE_ACTIONS(pe_snk_soft_reset), +/* Sink for PD30 */ +#ifdef CONFIG_USB_PD_REV30 + PE_STATE_ACTIONS(pe_snk_send_not_supported), + PE_STATE_ACTIONS(pe_snk_not_supported_received), + PE_STATE_ACTIONS(pe_snk_chunk_received), +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + PE_STATE_ACTIONS(pe_snk_source_alert_received), +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + PE_STATE_ACTIONS(pe_snk_send_sink_alert), +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + PE_STATE_ACTIONS(pe_snk_get_source_cap_ext), +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + PE_STATE_ACTIONS(pe_snk_get_source_status), +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + PE_STATE_ACTIONS(pe_snk_give_sink_status), +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + PE_STATE_ACTIONS(pe_snk_get_pps_status), +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SINK */ +/******************* DR_SWAP *******************/ +#ifdef CONFIG_USB_PD_DR_SWAP +/* DR_SWAP_DFP */ + PE_STATE_ACTIONS(pe_drs_dfp_ufp_evaluate_dr_swap), + PE_STATE_ACTIONS(pe_drs_dfp_ufp_accept_dr_swap), + PE_STATE_ACTIONS(pe_drs_dfp_ufp_change_to_ufp), + PE_STATE_ACTIONS(pe_drs_dfp_ufp_send_dr_swap), + PE_STATE_ACTIONS(pe_drs_dfp_ufp_reject_dr_swap), +/* DR_SWAP_UFP */ + PE_STATE_ACTIONS(pe_drs_ufp_dfp_evaluate_dr_swap), + PE_STATE_ACTIONS(pe_drs_ufp_dfp_accept_dr_swap), + PE_STATE_ACTIONS(pe_drs_ufp_dfp_change_to_dfp), + PE_STATE_ACTIONS(pe_drs_ufp_dfp_send_dr_swap), + PE_STATE_ACTIONS(pe_drs_ufp_dfp_reject_dr_swap), +#endif /* CONFIG_USB_PD_DR_SWAP */ +/******************* PR_SWAP *******************/ +#ifdef CONFIG_USB_PD_PR_SWAP +/* PR_SWAP_SRC */ + PE_STATE_ACTIONS(pe_prs_src_snk_evaluate_pr_swap), + PE_STATE_ACTIONS(pe_prs_src_snk_accept_pr_swap), + PE_STATE_ACTIONS(pe_prs_src_snk_transition_to_off), + PE_STATE_ACTIONS(pe_prs_src_snk_assert_rd), + PE_STATE_ACTIONS(pe_prs_src_snk_wait_source_on), + PE_STATE_ACTIONS(pe_prs_src_snk_send_swap), + PE_STATE_ACTIONS(pe_prs_src_snk_reject_pr_swap), + +/* PR_SWAP_SNK */ + PE_STATE_ACTIONS(pe_prs_snk_src_evaluate_pr_swap), + PE_STATE_ACTIONS(pe_prs_snk_src_accept_pr_swap), + PE_STATE_ACTIONS(pe_prs_snk_src_transition_to_off), + PE_STATE_ACTIONS(pe_prs_snk_src_assert_rp), + PE_STATE_ACTIONS(pe_prs_snk_src_source_on), + PE_STATE_ACTIONS(pe_prs_snk_src_send_swap), + PE_STATE_ACTIONS(pe_prs_snk_src_reject_swap), +/* get same role cap */ + PE_STATE_ACTIONS(pe_dr_src_get_source_cap), + PE_STATE_ACTIONS(pe_dr_src_give_sink_cap), + PE_STATE_ACTIONS(pe_dr_snk_get_sink_cap), + PE_STATE_ACTIONS(pe_dr_snk_give_source_cap), +/* get same role cap for PD30 */ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + PE_STATE_ACTIONS(pe_dr_snk_give_source_cap_ext), +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + PE_STATE_ACTIONS(pe_dr_src_get_source_cap_ext), +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PR_SWAP */ +/******************* VCONN_SWAP *******************/ +#ifdef CONFIG_USB_PD_VCONN_SWAP + PE_STATE_ACTIONS(pe_vcs_send_swap), + PE_STATE_ACTIONS(pe_vcs_evaluate_swap), + PE_STATE_ACTIONS(pe_vcs_accept_swap), + PE_STATE_ACTIONS(pe_vcs_reject_vconn_swap), + PE_STATE_ACTIONS(pe_vcs_wait_for_vconn), + PE_STATE_ACTIONS(pe_vcs_turn_off_vconn), + PE_STATE_ACTIONS(pe_vcs_turn_on_vconn), + PE_STATE_ACTIONS(pe_vcs_send_ps_rdy), +#endif /* CONFIG_USB_PD_VCONN_SWAP */ +/******************* UFP_VDM *******************/ + PE_STATE_ACTIONS(pe_ufp_vdm_get_identity), + PE_STATE_ACTIONS(pe_ufp_vdm_get_svids), + PE_STATE_ACTIONS(pe_ufp_vdm_get_modes), + PE_STATE_ACTIONS(pe_ufp_vdm_evaluate_mode_entry), + PE_STATE_ACTIONS(pe_ufp_vdm_mode_exit), + PE_STATE_ACTIONS(pe_ufp_vdm_attention_request), +#ifdef CONFIG_USB_PD_ALT_MODE + PE_STATE_ACTIONS(pe_ufp_vdm_dp_status_update), + PE_STATE_ACTIONS(pe_ufp_vdm_dp_configure), +#endif/* CONFIG_USB_PD_ALT_MODE */ +/******************* DFP_VDM *******************/ + PE_STATE_ACTIONS(pe_dfp_ufp_vdm_identity_request), + PE_STATE_ACTIONS(pe_dfp_ufp_vdm_identity_acked), + PE_STATE_ACTIONS(pe_dfp_ufp_vdm_identity_naked), + PE_STATE_ACTIONS(pe_dfp_cbl_vdm_identity_request), + PE_STATE_ACTIONS(pe_dfp_cbl_vdm_identity_acked), + PE_STATE_ACTIONS(pe_dfp_cbl_vdm_identity_naked), + PE_STATE_ACTIONS(pe_dfp_vdm_svids_request), + PE_STATE_ACTIONS(pe_dfp_vdm_svids_acked), + PE_STATE_ACTIONS(pe_dfp_vdm_svids_naked), + PE_STATE_ACTIONS(pe_dfp_vdm_modes_request), + PE_STATE_ACTIONS(pe_dfp_vdm_modes_acked), + PE_STATE_ACTIONS(pe_dfp_vdm_modes_naked), + PE_STATE_ACTIONS(pe_dfp_vdm_mode_entry_request), + PE_STATE_ACTIONS(pe_dfp_vdm_mode_entry_acked), + PE_STATE_ACTIONS(pe_dfp_vdm_mode_entry_naked), + PE_STATE_ACTIONS(pe_dfp_vdm_mode_exit_request), + PE_STATE_ACTIONS(pe_dfp_vdm_mode_exit_acked), + PE_STATE_ACTIONS(pe_dfp_vdm_attention_request), +#ifdef CONFIG_PD_DFP_RESET_CABLE + PE_STATE_ACTIONS(pe_dfp_cbl_send_soft_reset), + PE_STATE_ACTIONS(pe_dfp_cbl_send_cable_reset), +#endif /* CONFIG_PD_DFP_RESET_CABLE */ +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + PE_STATE_ACTIONS(pe_dfp_vdm_dp_status_update_request), + PE_STATE_ACTIONS(pe_dfp_vdm_dp_status_update_acked), + PE_STATE_ACTIONS(pe_dfp_vdm_dp_status_update_naked), + PE_STATE_ACTIONS(pe_dfp_vdm_dp_configuration_request), + PE_STATE_ACTIONS(pe_dfp_vdm_dp_configuration_acked), + PE_STATE_ACTIONS(pe_dfp_vdm_dp_configuration_naked), +#endif/* CONFIG_USB_PD_ALT_MODE_DFP */ +/******************* UVDM & SVDM *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_VDM + PE_STATE_ACTIONS(pe_ufp_uvdm_recv), + PE_STATE_ACTIONS(pe_dfp_uvdm_send), + PE_STATE_ACTIONS(pe_dfp_uvdm_acked), + PE_STATE_ACTIONS(pe_dfp_uvdm_naked), +#endif/* CONFIG_USB_PD_CUSTOM_VDM */ +/******************* PD30 Common *******************/ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE + PE_STATE_ACTIONS(pe_get_battery_cap), +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL + PE_STATE_ACTIONS(pe_give_battery_cap), +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE + PE_STATE_ACTIONS(pe_get_battery_status), +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL + PE_STATE_ACTIONS(pe_give_battery_status), +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + PE_STATE_ACTIONS(pe_get_manufacturer_info), +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + PE_STATE_ACTIONS(pe_give_manufacturer_info), +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE + PE_STATE_ACTIONS(pe_get_country_codes), +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL + PE_STATE_ACTIONS(pe_give_country_codes), +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE + PE_STATE_ACTIONS(pe_get_country_info), +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL + PE_STATE_ACTIONS(pe_give_country_info), +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ + PE_STATE_ACTIONS(pe_vdm_not_supported), +#endif /* CONFIG_USB_PD_REV30 */ +/******************* Others *******************/ +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + PE_STATE_ACTIONS(pe_dbg_ready), +#endif/* CONFIG_USB_PD_CUSTOM_DBGACC */ +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER + PE_STATE_ACTIONS(pe_over_recv_hreset_limit), +#endif/* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + PE_STATE_ACTIONS(pe_reject), + PE_STATE_ACTIONS(pe_error_recovery), +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE + PE_STATE_ACTIONS(pe_error_recovery_once), +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + PE_STATE_ACTIONS(pe_bist_test_data), + PE_STATE_ACTIONS(pe_bist_carrier_mode_2), +/* Wait tx finished */ + PE_STATE_ACTIONS(pe_idle1), + PE_STATE_ACTIONS(pe_idle2), +}; + +/* pd_state_action_fcn_t pe_get_exit_action(uint8_t pe_state) */ +void (*pe_get_exit_action(uint8_t pe_state)) + (struct pd_port *) +{ + void (*retval)(struct pd_port *) = NULL; + + switch (pe_state) { +/******************* Source *******************/ +#ifdef CONFIG_USB_PD_PE_SOURCE + case PE_SRC_TRANSITION_TO_DEFAULT: + retval = pe_src_transition_to_default_exit; + break; + case PE_SRC_GET_SINK_CAP: + retval = pe_src_get_sink_cap_exit; + break; +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + case PE_SRC_GET_SINK_STATUS: + retval = pe_src_get_sink_status_exit; + break; +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SOURCE */ + +/******************* Sink *******************/ +#ifdef CONFIG_USB_PD_PE_SINK + case PE_SNK_SELECT_CAPABILITY: + retval = pe_snk_select_capability_exit; + break; + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + case PE_SNK_GET_SOURCE_CAP_EXT: + retval = pe_snk_get_source_cap_ext_exit; + break; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + case PE_SNK_GET_SOURCE_STATUS: + retval = pe_snk_get_source_status_exit; + break; +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + case PE_SNK_GET_PPS_STATUS: + retval = pe_snk_get_pps_status_exit; + break; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PE_SINK */ + +/******************* PR_SWAP *******************/ +#ifdef CONFIG_USB_PD_PR_SWAP + case PE_DR_SRC_GET_SOURCE_CAP: + retval = pe_dr_src_get_source_cap_exit; + break; + + case PE_DR_SNK_GET_SINK_CAP: + retval = pe_dr_snk_get_sink_cap_exit; + break; + +/* get same role cap for PD30 */ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + case PE_DR_SRC_GET_SOURCE_CAP_EXT: + retval = pe_dr_src_get_source_cap_ext_exit; + break; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ +#endif /* CONFIG_USB_PD_PR_SWAP */ + +/******************* PD30 Common *******************/ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE + case PE_GET_BATTERY_CAP: + retval = pe_get_battery_cap_exit; + break; +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE + case PE_GET_BATTERY_STATUS: + retval = pe_get_battery_status_exit; + break; +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + case PE_GET_MANUFACTURER_INFO: + retval = pe_get_manufacturer_info_exit; + break; +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ + + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE + case PE_GET_COUNTRY_CODES: + retval = pe_get_country_codes_exit; + break; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE + case PE_GET_COUNTRY_INFO: + retval = pe_get_country_info_exit; + break; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ + +#endif /* CONFIG_USB_PD_REV30 */ + case PE_BIST_TEST_DATA: + retval = pe_bist_test_data_exit; + break; + case PE_BIST_CARRIER_MODE_2: + retval = pe_bist_carrier_mode_2_exit; + break; + default: + break; + } + + return retval; +} + +static inline void print_state( + struct pd_port *pd_port, uint8_t state) +{ + /* + * Source (P, Provider), Sink (C, Consumer) + * DFP (D), UFP (U) + * Vconn Source (Y/N) + */ + bool __maybe_unused vdm_evt = pd_curr_is_vdm_evt(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + +#if PE_DBG_ENABLE + PE_DBG("%s -> %s (%c%c%c)\n", + vdm_evt ? "VDM" : "PD", pe_state_name[state], + pd_port->power_role ? 'P' : 'C', + pd_port->data_role ? 'D' : 'U', + pd_port->vconn_role ? 'Y' : 'N'); +#else + PE_STATE_INFO("%s-> %s\n", + vdm_evt ? "VDM" : "PD", pe_state_name[state]); +#endif /* PE_DBG_ENABLE */ +} + +static void pe_reset_vdm_state_variable( + struct pd_port *pd_port, struct pe_data *pe_data) +{ + if (pe_data->vdm_state_timer) + pd_disable_timer(pd_port, pe_data->vdm_state_timer); + + pe_data->vdm_state_flags = 0; + pe_data->vdm_state_timer = 0; +} + +static inline void pd_pe_state_change( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + void (*prev_exit_action)(struct pd_port *pd_port); + void (*next_entry_action)(struct pd_port *pd_port); + struct pe_data *pe_data = &pd_port->pe_data; + + uint8_t old_state = pd_port->pe_state_curr; + uint8_t new_state = pd_port->pe_state_next; + + if (old_state >= PD_NR_PE_STATES || new_state >= PD_NR_PE_STATES) { + PD_BUG_ON(1); + return; + } + + if (new_state < PE_IDLE1) + prev_exit_action = pe_get_exit_action(old_state); + else + prev_exit_action = NULL; + + next_entry_action = pe_state_actions[new_state].entry_action; + +#if PE_STATE_INFO_VDM_DIS + if (!pd_curr_is_vdm_evt(pd_port)) +#endif /* PE_STATE_INFO_VDM_DIS */ + print_state(pd_port, new_state); + + if (pe_data->pe_state_flags & + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER) { + pd_disable_timer(pd_port, PD_TIMER_SENDER_RESPONSE); + } + + if (pd_curr_is_vdm_evt(pd_port)) + pe_reset_vdm_state_variable(pd_port, pe_data); + else if (pe_data->pe_state_timer) { + pd_disable_timer(pd_port, pe_data->pe_state_timer); + pe_data->pe_state_timer = 0; + } + + pe_data->pe_state_flags = 0; + pe_data->pe_state_flags2 = 0; + + if (prev_exit_action) + prev_exit_action(pd_port); + + if (next_entry_action) + next_entry_action(pd_port); + + if (pd_curr_is_vdm_evt(pd_port)) + pd_port->pe_vdm_state = new_state; + else + pd_port->pe_pd_state = new_state; + + pd_port->pe_state_curr = new_state; + + /* Change RX cap first for compliance */ + if (pd_port->state_machine > PE_STATE_MACHINE_NORMAL) + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_SWAP); +} + +static int pd_handle_event( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool dpm_imme; + struct pe_data *pe_data = &pd_port->pe_data; + + if (pd_curr_is_vdm_evt(pd_port)) { + dpm_imme = pd_port->pe_data.vdm_state_flags + & VDM_STATE_FLAG_DPM_ACK_IMMEDIATELY; + if ((pe_data->reset_vdm_state && (!dpm_imme)) || + (pd_event->event_type == PD_EVT_TCP_MSG)) { + pe_data->reset_vdm_state = false; + pd_port->pe_vdm_state = pd_port->pe_pd_state; + pe_reset_vdm_state_variable(pd_port, pe_data); + } + + pd_port->pe_state_curr = pd_port->pe_vdm_state; + } else { + pd_port->pe_state_curr = pd_port->pe_pd_state; + } + + if (pd_process_event(pd_port, pd_event)) + pd_pe_state_change(pd_port, pd_event); + + pd_free_event(pd_port->tcpc, pd_event); + return 1; +} + +/* + * Get Next Event + */ + +enum PE_NEW_EVT_TYPE { + PE_NEW_EVT_NULL = 0, + PE_NEW_EVT_PD = 1, + PE_NEW_EVT_VDM = 2, +}; + +static inline bool pd_try_get_vdm_event( + struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + bool ret = false; + struct pd_port *pd_port = &tcpc->pd_port; + + switch (pd_port->pe_pd_state) { +#ifdef CONFIG_USB_PD_PE_SINK + case PE_SNK_READY: + ret = pd_get_vdm_event(tcpc, pd_event); + break; +#endif /* CONFIG_USB_PD_PE_SINK */ + +#ifdef CONFIG_USB_PD_PE_SOURCE + case PE_SRC_READY: + ret = pd_get_vdm_event(tcpc, pd_event); + break; + case PE_SRC_STARTUP: + ret = pd_get_vdm_event(tcpc, pd_event); + break; + case PE_SRC_DISCOVERY: + ret = pd_get_vdm_event(tcpc, pd_event); + break; + +#ifdef CONFIG_PD_SRC_RESET_CABLE + case PE_SRC_CBL_SEND_SOFT_RESET: + ret = pd_get_vdm_event(tcpc, pd_event); + break; +#endif /* CONFIG_PD_SRC_RESET_CABLE */ +#endif /* CONFIG_USB_PD_PE_SOURCE */ + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + case PE_DBG_READY: + ret = pd_get_vdm_event(tcpc, pd_event); + break; +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + case PE_IDLE1: + ret = pd_get_vdm_event(tcpc, pd_event); + break; + default: + break; + } + + return ret; +} + +#ifdef CONFIG_USB_PD_REV30 + +static inline bool pd_check_sink_tx_ok(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + if (pd_check_rev30(pd_port) && + (pd_port->pe_data.pd_traffic_control != PD_SINK_TX_OK)) + return false; +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + + return true; +} + +static inline bool pd_check_source_tx_ok(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + if (!pd_check_rev30(pd_port)) + return true; + + if (pd_port->pe_data.pd_traffic_control == PD_SOURCE_TX_OK) + return true; + + if (!pd_port->pe_data.pd_traffic_idle) + pd_set_sink_tx(pd_port, PD30_SINK_TX_NG); + + return false; +#else + return true; +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +} + +static inline bool pd_check_pd30_tx_ready(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_PE_SINK + if (pd_port->pe_pd_state == PE_SNK_READY) + return pd_check_sink_tx_ok(pd_port); +#endif /* CONFIG_USB_PD_PE_SINK */ + +#ifdef CONFIG_USB_PD_PE_SOURCE + if (pd_port->pe_pd_state == PE_SRC_READY) + return pd_check_source_tx_ok(pd_port); +#endif /* CONFIG_USB_PD_PE_SOURCE */ + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + if (pd_port->pe_pd_state == PE_DBG_READY) + return true; +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + + return false; +} +#else +static inline bool pd_check_pd20_tx_ready(struct pd_port *pd_port) +{ + switch (pd_port->pe_pd_state) { +#ifdef CONFIG_USB_PD_PE_SINK + case PE_SNK_READY: + return true; +#endif /* CONFIG_USB_PD_PE_SINK */ + +#ifdef CONFIG_USB_PD_PE_SOURCE + case PE_SRC_READY: + return true; +#endif /* CONFIG_USB_PD_PE_SOURCE */ + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + case PE_DBG_READY: + return true; +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + + default: + return false; + } +} +#endif /* ndef CONFIG_USB_PD_REV30 */ + +/** + * pd_check_tx_ready + * + * Check PE is ready to initiate an active event (AMS). + * + * For revision 2, checking the PE state is in Ready. + * For revision 3, checking SinkTx besides above description. + * + * If PR=SRC and CC=SinkTxOK, change CC to SinkTxNG; + * + * Returns a boolean value to present PE is available or not. + */ + +static inline bool pd_check_tx_ready(struct pd_port *pd_port) +{ + /* VDM BUSY : Waiting for response */ + if (pd_port->pe_data.vdm_state_timer) + return false; + +#ifdef CONFIG_USB_PD_REV30 + return pd_check_pd30_tx_ready(pd_port); +#else + return pd_check_pd20_tx_ready(pd_port); +#endif /* CONFIG_USB_PD_REV30 */ +} + +/** + * pd_try_get_deferred_tcp_event + * + * Get a pending TCPM event from the event queue + * + * Returns TCP_DPM_EVT_ID if succeeded, + * otherwise returns DPM_READY_REACTION_BUSY. + */ + +static inline uint8_t pd_try_get_deferred_tcp_event(struct pd_port *pd_port) +{ + if (!pd_get_deferred_tcp_event( + pd_port->tcpc, &pd_port->tcp_event)) + return DPM_READY_REACTION_BUSY; + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + pd_port->tcp_event_drop_reset_once = true; +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + + pd_port->tcp_event_id_1st = pd_port->tcp_event.event_id; + return pd_port->tcp_event_id_1st; +} + +/** + * pd_try_get_active_event + * + * Get a pending active event if TX is available + * + * Event Priority : + * DPM reactions, TCPM request. + * + * Returns PE_NEW_EVT_TYPE. + */ + +static inline uint8_t pd_try_get_active_event( + struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + uint8_t ret; + uint8_t from_pe = PD_TCP_FROM_PE; + struct pd_port *pd_port = &tcpc->pd_port; + + if (!pd_check_tx_ready(pd_port)) + return PE_NEW_EVT_NULL; + + ret = pd_dpm_get_ready_reaction(pd_port); + + if (ret == 0) { + from_pe = PD_TCP_FROM_TCPM; + ret = pd_try_get_deferred_tcp_event(pd_port); + } + +#if DPM_DBG_ENABLE + if ((ret != 0) && (ret != DPM_READY_REACTION_BUSY)) { + DPM_DBG("from_pe: %d, evt:%d, reaction:0x%x\n", + from_pe, ret, pd_port->pe_data.dpm_reaction_id); + } +#endif /* DPM_DBG_ENABLE */ + + if (ret == DPM_READY_REACTION_BUSY) + return PE_NEW_EVT_NULL; + + pd_event->event_type = PD_EVT_TCP_MSG; + pd_event->msg = ret; + pd_event->msg_sec = from_pe; + pd_event->pd_msg = NULL; + + if (ret >= TCP_DPM_EVT_VDM_COMMAND) + return PE_NEW_EVT_VDM; + + return PE_NEW_EVT_PD; +} + +/** + * pd_try_get_next_event + * + * Get a pending event + * + * Event Priority : + * PD state machine's event, VDM state machine's event, + * Active event (DPM reactions, TCPM request) + * + * Returns PE_NEW_EVT_TYPE. + */ + +static inline uint8_t pd_try_get_next_event( + struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + uint8_t ret = 0; + struct pd_port *pd_port = &tcpc->pd_port; + + if (pd_get_event(tcpc, pd_event)) + return PE_NEW_EVT_PD; + + if (pd_try_get_vdm_event(tcpc, pd_event)) + return PE_NEW_EVT_VDM; + + mutex_lock(&pd_port->pd_lock); + ret = pd_try_get_active_event(tcpc, pd_event); + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + +/* + * Richtek Policy Engine + */ + +static inline int pd_handle_dpm_immediately( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool dpm_immediately; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_curr_is_vdm_evt(pd_port)) { + dpm_immediately = pd_port->pe_data.vdm_state_flags + & VDM_STATE_FLAG_DPM_ACK_IMMEDIATELY; + } else { + dpm_immediately = pd_port->pe_data.pe_state_flags2 + & PE_STATE_FLAG_DPM_ACK_IMMEDIATELY; + } + + if (dpm_immediately) { + PE_DBG("DPM_Immediately\n"); + pd_event->event_type = PD_EVT_DPM_MSG; + pd_event->msg = PD_DPM_ACK; + return pd_handle_event(pd_port, pd_event); + } + + return false; +} + +int pd_policy_engine_run(struct tcpc_device *tcpc) +{ + bool loop = true; + uint8_t ret; + struct pd_port *pd_port = &tcpc->pd_port; + struct pd_event *pd_event = pd_get_curr_pd_event(pd_port); + + ret = pd_try_get_next_event(tcpc, pd_event); + if (ret == PE_NEW_EVT_NULL) { + loop = false; + goto out; + } + + mutex_lock(&pd_port->pd_lock); + + pd_port->curr_is_vdm_evt = (ret == PE_NEW_EVT_VDM); + + pd_handle_event(pd_port, pd_event); + pd_handle_dpm_immediately(pd_port, pd_event); + + mutex_unlock(&pd_port->pd_lock); +out: + return loop; +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_com.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_com.c new file mode 100644 index 000000000000..7db2d2800538 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_com.c @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for Common + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +static inline void *pd_get_tcp_event_data_object(struct pd_port *pd_port) +{ + return pd_port->tcp_event.tcp_dpm_data.data_object; +} + +/* + * Policy Engine General State Activity + */ + +static void pe_idle_reset_data(struct pd_port *pd_port) +{ + pd_reset_pe_timer(pd_port); + pd_reset_svid_data(pd_port); + + pd_port->pe_data.pd_prev_connected = false; + pd_port->state_machine = PE_STATE_MACHINE_IDLE; + + pd_enable_bist_test_mode(pd_port, false); + +#ifndef CONFIG_USB_PD_TRANSMIT_BIST2 + pd_disable_bist_mode2(pd_port); +#endif /* CONFIG_USB_PD_TRANSMIT_BIST2 */ + + pd_enable_timer(pd_port, PD_TIMER_PE_IDLE_TOUT); + + pd_unlock_msg_output(pd_port); +} + +void pe_idle1_entry(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE + pd_port->error_recovery_once = 0; +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + + pe_idle_reset_data(pd_port); + pd_try_put_pe_idle_event(pd_port); +} + +void pe_idle2_entry(struct pd_port *pd_port) +{ + memset(&pd_port->pe_data, 0, sizeof(struct pe_data)); + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_IDLE); + pd_disable_timer(pd_port, PD_TIMER_PE_IDLE_TOUT); + pd_notify_pe_idle(pd_port); +} + +void pe_reject_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_REJECT); +} + +void pe_error_recovery_entry(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE + pd_port->error_recovery_once++; +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + + pe_idle_reset_data(pd_port); + pd_notify_pe_error_recovery(pd_port); + pd_try_put_pe_idle_event(pd_port); +} + +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE +void pe_error_recovery_once_entry(struct pd_port *pd_port) +{ + uint8_t state = PD_CONNECT_TYPEC_ONLY_SNK_DFT; + + pd_notify_pe_hard_reset_completed(pd_port); + + if (pd_port->power_role == PD_ROLE_SOURCE) { + state = PD_CONNECT_TYPEC_ONLY_SRC; + pd_dpm_dynamic_disable_vconn(pd_port); + } else + pd_dpm_sink_vbus(pd_port, true); + + pd_update_connect_state(pd_port, state); +} +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER +void pe_over_recv_hreset_limit_entry(struct pd_port *pd_port) +{ + PE_INFO("OverHResetLimit++\n"); + pe_idle_reset_data(pd_port); + pd_notify_pe_over_recv_hreset(pd_port); + PE_INFO("OverHResetLimit--\n"); +} +#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + +void pe_bist_test_data_entry(struct pd_port *pd_port) +{ + PE_STATE_IGNORE_UNKNOWN_EVENT(pd_port); + + pd_enable_bist_test_mode(pd_port, true); +} + +void pe_bist_test_data_exit(struct pd_port *pd_port) +{ + pd_enable_bist_test_mode(pd_port, false); +} + +void pe_bist_carrier_mode_2_entry(struct pd_port *pd_port) +{ + pd_send_bist_mode2(pd_port); + pd_enable_pe_state_timer(pd_port, PD_TIMER_BIST_CONT_MODE); +} + +void pe_bist_carrier_mode_2_exit(struct pd_port *pd_port) +{ + pd_disable_bist_mode2(pd_port); +} + +/* + * Policy Engine Share State Activity + */ + +static inline uint8_t pe30_power_ready_entry(struct pd_port *pd_port) +{ + uint8_t rx_cap = PD_RX_CAP_PE_READY_UFP; + + if (pd_port->vconn_role) + rx_cap = PD_RX_CAP_PE_READY_DFP; + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + dpm_reaction_clear(pd_port, + DPM_REACTION_DFP_FLOW_DELAY | + DPM_REACTION_UFP_FLOW_DELAY); +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + + return rx_cap; +} + +static inline uint8_t pe20_power_ready_entry(struct pd_port *pd_port) +{ + uint8_t rx_cap = PD_RX_CAP_PE_READY_UFP; + + if (pd_port->data_role == PD_ROLE_DFP) + rx_cap = PD_RX_CAP_PE_READY_DFP; + + pd_port->pe_data.pe_state_flags |= + PE_STATE_FLAG_IGNORE_UNKNOWN_EVENT; + + return rx_cap; +} + +void pe_power_ready_entry(struct pd_port *pd_port) +{ + uint8_t rx_cap; + + pd_port->state_machine = PE_STATE_MACHINE_NORMAL; + + pd_port->pe_data.during_swap = false; + pd_port->pe_data.explicit_contract = true; + +#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER + pd_port->pe_data.renegotiation_count = 0; +#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */ + + if (pd_check_rev30(pd_port)) + rx_cap = pe30_power_ready_entry(pd_port); + else + rx_cap = pe20_power_ready_entry(pd_port); + + pd_set_rx_enable(pd_port, rx_cap); + + dpm_reaction_set(pd_port, DPM_REACTION_CAP_READY_ONCE); + pd_notify_tcp_event_2nd_result(pd_port, TCP_DPM_RET_SUCCESS); +} + +#ifdef CONFIG_USB_PD_REV30 + +/* + * [PD3.0] Figure 8-85 Get Battery Capabilities State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE +void pe_get_battery_cap_entry(struct pd_port *pd_port) +{ + struct pd_get_battery_capabilities *gbcdb = + pd_get_tcp_event_data_object(pd_port); + + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_ext_msg(pd_port, + PD_EXT_GET_BAT_CAP, PD_GBCDB_SIZE, gbcdb); +} + +void pe_get_battery_cap_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_battery_cap(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ + +/* + * [PD3.0] Figure 8-86 Give Battery Capabilities State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL +void pe_give_battery_cap_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_battery_cap(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ + +/* + * [PD3.0] Figure 8-87 Get Battery Status State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE +void pe_get_battery_status_entry(struct pd_port *pd_port) +{ + struct pd_get_battery_status *gbsdb = + pd_get_tcp_event_data_object(pd_port); + + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_ext_msg(pd_port, + PD_EXT_GET_BAT_STATUS, PD_GBSDB_SIZE, gbsdb); +} + +void pe_get_battery_status_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_battery_status(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ + +/* + * [PD3.0] Figure 8-88 Give Battery Status State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL +void pe_give_battery_status_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_battery_status(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ + +/* + * [PD3.0] Figure 8-89 Get Manufacturer Information State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + +void pe_get_manufacturer_info_entry(struct pd_port *pd_port) +{ + struct pd_get_manufacturer_info *gmidb = + pd_get_tcp_event_data_object(pd_port); + + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_ext_msg(pd_port, + PD_EXT_GET_MFR_INFO, PD_GMIDB_SIZE, gmidb); +} + +void pe_get_manufacturer_info_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_mfrs_info(pd_port); +} + +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ + +/* + * [PD3.0] Figure 8-90 Give Manufacturer Information State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + +void pe_give_manufacturer_info_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_mfrs_info(pd_port); +} + +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + +/* + * [PD3.0] Figure 8-91 Get Country Codes State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE +void pe_get_country_codes_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_COUNTRY_CODE); +} + +void pe_get_country_codes_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_country_codes(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ + +/* + * [PD3.0] Figure 8-92 Give Country Codes State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL +void pe_give_country_codes_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_country_codes(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ + +/* + * [PD3.0] Figure 8-93 Get Country Information State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE +void pe_get_country_info_entry(struct pd_port *pd_port) +{ + uint32_t *ccdo = + pd_get_tcp_event_data_object(pd_port); + + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_data_msg(pd_port, + PD_DATA_GET_COUNTRY_INFO, PD_CCDO_SIZE, ccdo); +} + +void pe_get_country_info_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_country_info(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ + +/* + * [PD3.0] Figure 8-94 Give Country Information State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL +void pe_give_country_info_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_country_info(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */ + +/* + * [PD3.0] Unsupported, Unrecognized UVDM and Unsupported SVDM. + */ + +void pe_vdm_not_supported_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_NOT_SUPPORTED); +} + +#endif /* CONFIG_USB_PD_REV30 */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dbg.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dbg.c new file mode 100644 index 000000000000..fa9abce905f4 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dbg.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for DBGACC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + +void pe_dbg_ready_entry(struct pd_port *pd_port) +{ + uint8_t state; + + if (pd_port->pe_data.pe_ready) + return; + + pd_port->pe_data.pe_ready = true; + pd_reset_protocol_layer(pd_port, false); + + if (pd_port->data_role == PD_ROLE_UFP) { + PE_INFO("Custom_DBGACC : UFP\n"); + state = PD_CONNECT_PE_READY_DBGACC_UFP; + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_READY_UFP); + } else { + PE_INFO("Custom_DBGACC : DFP\n"); + state = PD_CONNECT_PE_READY_DBGACC_DFP; + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_READY_DFP); + } + + pd_update_connect_state(pd_port, state); +} + +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dfp.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dfp.c new file mode 100644 index 000000000000..caedc29c105e --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dfp.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for DFP + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +/* + * [PD2.0] Figure 8-64 DFP to UFP VDM Discover Identity State Diagram + */ + +void pe_dfp_ufp_vdm_identity_request_entry(struct pd_port *pd_port) +{ + pd_send_vdm_discover_id(pd_port, TCPC_TX_SOP); +} + +void pe_dfp_ufp_vdm_identity_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_id(pd_port, true); +} + +void pe_dfp_ufp_vdm_identity_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_id(pd_port, false); +} + +/* + * [PD2.0] Figure 8-65 DFP VDM Discover Identity State Diagram + */ + +void pe_dfp_cbl_vdm_identity_request_entry(struct pd_port *pd_port) +{ + pd_port->pe_data.discover_id_counter++; + pd_send_vdm_discover_id(pd_port, TCPC_TX_SOP_PRIME); +} + +void pe_dfp_cbl_vdm_identity_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_inform_cable_id(pd_port, false); +} + +void pe_dfp_cbl_vdm_identity_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_inform_cable_id(pd_port, false); +} + +/* + * [PD2.0] Figure 8-66 DFP VDM Discover SVIDs State Diagram + */ + +void pe_dfp_vdm_svids_request_entry(struct pd_port *pd_port) +{ + pd_send_vdm_discover_svids(pd_port, TCPC_TX_SOP); +} + +void pe_dfp_vdm_svids_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_svids(pd_port, true); +} + +void pe_dfp_vdm_svids_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_svids(pd_port, false); +} + +/* + * [PD2.0] Figure 8-67 DFP VDM Discover Modes State Diagram + */ + +void pe_dfp_vdm_modes_request_entry(struct pd_port *pd_port) +{ + pd_send_vdm_discover_modes(pd_port, TCPC_TX_SOP, pd_port->mode_svid); +} + +void pe_dfp_vdm_modes_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_modes(pd_port, true); +} + +void pe_dfp_vdm_modes_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_modes(pd_port, false); +} + +/* + * [PD2.0] Figure 8-68 DFP VDM Mode Entry State Diagram + */ + +void pe_dfp_vdm_mode_entry_request_entry(struct pd_port *pd_port) +{ + pd_send_vdm_enter_mode(pd_port, TCPC_TX_SOP, + pd_port->mode_svid, pd_port->mode_obj_pos); +} + +void pe_dfp_vdm_mode_entry_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_enter_mode(pd_port, true); +} + +void pe_dfp_vdm_mode_entry_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_enter_mode(pd_port, false); +} + +/* + * [PD2.0] Figure 8-69 DFP VDM Mode Exit State Diagram + */ + +void pe_dfp_vdm_mode_exit_request_entry(struct pd_port *pd_port) +{ + pd_send_vdm_exit_mode(pd_port, TCPC_TX_SOP, + pd_port->mode_svid, pd_port->mode_obj_pos); +} + +void pe_dfp_vdm_mode_exit_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_exit_mode(pd_port); +} + +/* + * [PD2.0] Figure 8-70 DFP VDM Attention State Diagram + */ + +void pe_dfp_vdm_attention_request_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_attention(pd_port); +} + +/* + * [PD2.0] Figure 8-83 DFP Cable Soft Reset or Cable Reset State Diagram + */ + +#ifdef CONFIG_PD_DFP_RESET_CABLE + +void pe_dfp_cbl_send_soft_reset_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG_OR_TX_FAILED(pd_port); + + pd_send_cable_soft_reset(pd_port); +} + +void pe_dfp_cbl_send_cable_reset_entry(struct pd_port *pd_port) +{ + /* TODO : we don't do cable reset now */ +} + +#endif /* CONFIG_PD_DFP_RESET_CABLE */ + +/* + * [PD2.0] Display Port + */ + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + +void pe_dfp_vdm_dp_status_update_request_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_send_dp_status_update(pd_port); +} + +void pe_dfp_vdm_dp_status_update_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_dp_status_update(pd_port, true); +} + +void pe_dfp_vdm_dp_status_update_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_dp_status_update(pd_port, false); +} + +void pe_dfp_vdm_dp_configuration_request_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_send_dp_configuration(pd_port); +} + +void pe_dfp_vdm_dp_configuration_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_dp_configuration(pd_port, true); +} + +void pe_dfp_vdm_dp_configuration_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_dp_configuration(pd_port, false); +} + +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + +/* + * UVDM + */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + +void pe_dfp_uvdm_send_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_send_uvdm(pd_port); +} + +void pe_dfp_uvdm_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_uvdm(pd_port, true); +} + +void pe_dfp_uvdm_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_dfp_inform_uvdm(pd_port, false); +} + +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dr.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dr.c new file mode 100644 index 000000000000..c9d305ddf64c --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_dr.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for DR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +/* + * [PD2.0] + * Figure 8-53 Dual-Role (Source) Get Source Capabilities diagram + * Figure 8-54 Dual-Role (Source) Give Sink Capabilities diagram + * Figure 8-55 Dual-Role (Sink) Get Sink Capabilities State Diagram + * Figure 8-56 Dual-Role (Sink) Give Source Capabilities State Diagram + */ + +void pe_dr_src_get_source_cap_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG_OR_RJ(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SOURCE_CAP); +} + +void pe_dr_src_get_source_cap_exit(struct pd_port *pd_port) +{ + pd_dpm_dr_inform_source_cap(pd_port); +} + +void pe_dr_src_give_sink_cap_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_sink_caps(pd_port); +} + +void pe_dr_snk_get_sink_cap_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG_OR_RJ(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SINK_CAP); +} + +void pe_dr_snk_get_sink_cap_exit(struct pd_port *pd_port) +{ + pd_dpm_dr_inform_sink_cap(pd_port); +} + +void pe_dr_snk_give_source_cap_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_source_caps(pd_port); +} + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL +void pe_dr_snk_give_source_cap_ext_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_source_cap_ext(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE +void pe_dr_src_get_source_cap_ext_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SOURCE_CAP_EXT); +} + +void pe_dr_src_get_source_cap_ext_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_source_cap_ext(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_drs.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_drs.c new file mode 100644 index 000000000000..50e149817903 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_drs.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for DRS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +/* + * [PD2.0] Figure 8-49: Type-C DFP to UFP Data Role Swap State Diagram + */ + +void pe_drs_dfp_ufp_evaluate_dr_swap_entry(struct pd_port *pd_port) +{ + pd_dpm_drs_evaluate_swap(pd_port, PD_ROLE_UFP); +} + +void pe_drs_dfp_ufp_accept_dr_swap_entry(struct pd_port *pd_port) +{ + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT); +} + +void pe_drs_dfp_ufp_change_to_ufp_entry(struct pd_port *pd_port) +{ + pd_dpm_drs_change_role(pd_port, PD_ROLE_UFP); +} + +void pe_drs_dfp_ufp_send_dr_swap_entry(struct pd_port *pd_port) +{ + pe_send_swap_request_entry(pd_port, PD_CTRL_DR_SWAP); +} + +void pe_drs_dfp_ufp_reject_dr_swap_entry(struct pd_port *pd_port) +{ + pd_reply_wait_reject_msg(pd_port); +} + +/* + * [PD2.0] Figure 8-50: Type-C UFP to DFP Data Role Swap State Diagram + */ + +void pe_drs_ufp_dfp_evaluate_dr_swap_entry(struct pd_port *pd_port) +{ + pd_dpm_drs_evaluate_swap(pd_port, PD_ROLE_DFP); +} + +void pe_drs_ufp_dfp_accept_dr_swap_entry(struct pd_port *pd_port) +{ + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT); +} + +void pe_drs_ufp_dfp_change_to_dfp_entry(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_RESET_CABLE + dpm_reaction_set(pd_port, DPM_REACTION_CAP_RESET_CABLE); +#endif /* CONFIG_USB_PD_RESET_CABLE */ + + pd_dpm_drs_change_role(pd_port, PD_ROLE_DFP); +} + +void pe_drs_ufp_dfp_send_dr_swap_entry(struct pd_port *pd_port) +{ + pe_send_swap_request_entry(pd_port, PD_CTRL_DR_SWAP); +} + +void pe_drs_ufp_dfp_reject_dr_swap_entry( + struct pd_port *pd_port) +{ + pd_reply_wait_reject_msg(pd_port); +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_prs.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_prs.c new file mode 100644 index 000000000000..ac766c773412 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_prs.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for PRS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +/* + * [PD2.0] Figure 8-51: + * Dual-Role Port in Source to Sink Power Role Swap State Diagram + */ + +void pe_prs_src_snk_evaluate_pr_swap_entry(struct pd_port *pd_port) +{ + pd_dpm_prs_evaluate_swap(pd_port, PD_ROLE_SINK); +} + +void pe_prs_src_snk_accept_pr_swap_entry(struct pd_port *pd_port) +{ + pd_notify_pe_execute_pr_swap(pd_port, true); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT); +} + +void pe_prs_src_snk_transition_to_off_entry(struct pd_port *pd_port) +{ + pd_lock_msg_output(pd_port); /* for tSRCTransition */ + pd_notify_pe_execute_pr_swap(pd_port, true); + + pd_enable_timer(pd_port, PD_TIMER_SOURCE_TRANSITION); +} + +void pe_prs_src_snk_assert_rd_entry(struct pd_port *pd_port) +{ + pd_dpm_prs_change_role(pd_port, PD_ROLE_SINK); +} + +void pe_prs_src_snk_wait_source_on_entry(struct pd_port *pd_port) +{ + PE_STATE_HRESET_IF_TX_FAILED(pd_port); + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_PS_RDY); +} + +void pe_prs_src_snk_send_swap_entry(struct pd_port *pd_port) +{ + pe_send_swap_request_entry(pd_port, PD_CTRL_PR_SWAP); +} + +void pe_prs_src_snk_reject_pr_swap_entry(struct pd_port *pd_port) +{ + pd_reply_wait_reject_msg(pd_port); +} + +/* + * [PD2.0] Figure 8-52: + * Dual-role Port in Sink to Source Power Role Swap State Diagram + */ + +void pe_prs_snk_src_evaluate_pr_swap_entry(struct pd_port *pd_port) +{ + pd_dpm_prs_evaluate_swap(pd_port, PD_ROLE_SOURCE); +} + +void pe_prs_snk_src_accept_pr_swap_entry( + struct pd_port *pd_port) +{ + pd_notify_pe_execute_pr_swap(pd_port, true); + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT); +} + +void pe_prs_snk_src_transition_to_off_entry(struct pd_port *pd_port) +{ + /* + * Sink should call pd_notify_pe_execute_pr_swap before this state, + * because source may turn off power & change CC before we got + * GoodCRC or Accept. + */ + + pd_port->pe_data.during_swap = true; + pd_enable_pe_state_timer(pd_port, PD_TIMER_PS_SOURCE_OFF); + pd_dpm_prs_turn_off_power_sink(pd_port); +} + +void pe_prs_snk_src_assert_rp_entry(struct pd_port *pd_port) +{ + pd_dpm_prs_change_role(pd_port, PD_ROLE_SOURCE); +} + +void pe_prs_snk_src_source_on_entry(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_RESET_CABLE + dpm_reaction_set(pd_port, DPM_REACTION_CAP_RESET_CABLE); +#endif /* CONFIG_USB_PD_RESET_CABLE */ + + PE_STATE_HRESET_IF_TX_FAILED(pd_port); + pd_dpm_dynamic_enable_vconn(pd_port); + pd_dpm_prs_enable_power_source(pd_port, true); + + /* Send PS_Rdy in process_event after source_on */ +} + +void pe_prs_snk_src_send_swap_entry(struct pd_port *pd_port) +{ + pd_notify_pe_execute_pr_swap(pd_port, false); + pe_send_swap_request_entry(pd_port, PD_CTRL_PR_SWAP); +} + +void pe_prs_snk_src_reject_swap_entry(struct pd_port *pd_port) +{ + pd_reply_wait_reject_msg(pd_port); +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_snk.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_snk.c new file mode 100644 index 000000000000..f9ade6e40f20 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_snk.c @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for SNK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +/* + * [PD2.0] Figure 8-39 Sink Port state diagram + */ + +void pe_snk_startup_entry(struct pd_port *pd_port) +{ + uint8_t rx_cap = PD_RX_CAP_PE_STARTUP; + bool pr_swap = pd_port->state_machine == PE_STATE_MACHINE_PR_SWAP; + +#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP + uint8_t msg_id_last = pd_port->pe_data.msg_id_rx[TCPC_TX_SOP]; +#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */ + + pd_reset_protocol_layer(pd_port, false); + + if (pr_swap) { + /* + * If PE reset rx_cap to startup in here, + * maybe can't meet tSwapSink for PR_SWAP case + */ + rx_cap = PD_RX_CAP_PE_SEND_WAIT_CAP; + +#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP + pd_port->msg_id_pr_swap_last = msg_id_last; +#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */ + } + + pd_set_rx_enable(pd_port, rx_cap); + pd_put_pe_event(pd_port, PD_PE_RESET_PRL_COMPLETED); +} + +void pe_snk_discovery_entry(struct pd_port *pd_port) +{ + bool wait_valid = true; + + if (pd_check_pe_during_hard_reset(pd_port)) { + wait_valid = false; + pd_enable_pe_state_timer(pd_port, PD_TIMER_PS_TRANSITION); + } + pd_enable_vbus_valid_detection(pd_port, wait_valid); +} + +void pe_snk_wait_for_capabilities_entry( + struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW + /* Default current draw after HardReset */ + if (pd_check_pe_during_hard_reset(pd_port)) + pd_dpm_sink_vbus(pd_port, true); +#endif /* CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW */ + + pd_notify_pe_hard_reset_completed(pd_port); + + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_SEND_WAIT_CAP); + pd_enable_pe_state_timer(pd_port, PD_TIMER_SINK_WAIT_CAP); +} + +void pe_snk_evaluate_capability_entry(struct pd_port *pd_port) +{ + /* Disable UART output for Source SenderResponse */ + pd_lock_msg_output(pd_port); + + pd_handle_hard_reset_recovery(pd_port); + pd_handle_first_pd_command(pd_port); + + pd_port->pe_data.explicit_contract = false; + pd_dpm_snk_evaluate_caps(pd_port); +} + +void pe_snk_select_capability_entry(struct pd_port *pd_port) +{ + struct pd_event *pd_event = pd_get_curr_pd_event(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + PE_STATE_WAIT_MSG_HRESET_IF_TOUT(pd_port); + + if (pd_event->event_type == PD_EVT_DPM_MSG) { + PE_DBG("SelectCap%d, rdo:0x%08x\n", + pd_event->msg_sec, pd_port->last_rdo); + } else { + /* new request, for debug only */ + /* pd_dpm_sink_vbus(pd_port, false); */ + PE_DBG("NewReq, rdo:0x%08x\n", pd_port->last_rdo); + } + + /* Disable UART output for Sink SenderResponse */ + pd_lock_msg_output(pd_port); + + pd_send_sop_data_msg(pd_port, + PD_DATA_REQUEST, 1, &pd_port->last_rdo); +} + +void pe_snk_select_capability_exit(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; +#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */ + + if (pd_check_ctrl_msg_event(pd_port, PD_CTRL_ACCEPT)) { + pd_port->pe_data.remote_selected_cap = + RDO_POS(pd_port->last_rdo); + pd_port->cap_miss_match = 0; + } else if (pd_check_ctrl_msg_event(pd_port, PD_CTRL_REJECT)) { +#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER + if (pd_port->cap_miss_match == 0x01) { + PE_INFO("reset renegotiation cnt by cap mismatch\n"); + pd_port->pe_data.renegotiation_count = 0; + } +#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */ + pd_port->cap_miss_match |= (1 << 1); + } else + pd_port->cap_miss_match = 0; + + /* Waiting for Hard-Reset Done */ + if (!pd_check_timer_msg_event(pd_port, PD_TIMER_SENDER_RESPONSE)) + pd_unlock_msg_output(pd_port); +} + +void pe_snk_transition_sink_entry(struct pd_port *pd_port) +{ + pd_enable_pe_state_timer(pd_port, PD_TIMER_PS_TRANSITION); + +#ifdef CONFIG_USB_PD_SNK_GOTOMIN + if (pd_check_ctrl_msg_event(pd_port, PD_CTRL_GOTO_MIN)) { + if (pd_port->dpm_caps & DPM_CAP_LOCAL_GIVE_BACK) + pd_port->request_i_new = pd_port->request_i_op; + } +#endif /* CONFIG_USB_PD_SNK_GOTOMIN */ + + pd_dpm_snk_standby_power(pd_port); +} + +void pe_snk_ready_entry(struct pd_port *pd_port) +{ + if (pd_check_ctrl_msg_event(pd_port, PD_CTRL_WAIT)) + pd_enable_timer(pd_port, PD_TIMER_SINK_REQUEST); + + pd_notify_pe_snk_explicit_contract(pd_port); + pe_power_ready_entry(pd_port); +} + +void pe_snk_hard_reset_entry(struct pd_port *pd_port) +{ + pd_send_hard_reset(pd_port); +} + +void pe_snk_transition_to_default_entry(struct pd_port *pd_port) +{ + pd_reset_local_hw(pd_port); + pd_dpm_snk_hard_reset(pd_port); +} + +void pe_snk_give_sink_cap_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_sink_caps(pd_port); +} + +void pe_snk_get_source_cap_entry(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + PE_STATE_WAIT_MSG(pd_port); +#else + PE_STATE_WAIT_TX_SUCCESS(pd_port); +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SOURCE_CAP); +} + +void pe_snk_send_soft_reset_entry(struct pd_port *pd_port) +{ + pd_send_soft_reset(pd_port); +} + +void pe_snk_soft_reset_entry(struct pd_port *pd_port) +{ + pd_handle_soft_reset(pd_port); +} + +/* ---- Policy Engine (PD30) ---- */ + +#ifdef CONFIG_USB_PD_REV30 + +/* + * [PD3.0] Figure 8-71 Sink Port Not Supported Message State Diagram + */ + +void pe_snk_send_not_supported_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_NOT_SUPPORTED); +} + +void pe_snk_not_supported_received_entry(struct pd_port *pd_port) +{ + PE_STATE_DPM_INFORMED(pd_port); + + pd_dpm_inform_not_support(pd_port); +} + +void pe_snk_chunk_received_entry(struct pd_port *pd_port) +{ + pd_enable_timer(pd_port, PD_TIMER_CK_NOT_SUPPORTED); +} + +/* + * [PD3.0] Figure 8-74 Sink Port Source Alert State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +void pe_snk_source_alert_received_entry(struct pd_port *pd_port) +{ + PE_STATE_DPM_INFORMED(pd_port); + + pd_dpm_inform_alert(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +/* + * [PD3.0] Figure 8-75 Sink Port Sink Alert State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL +void pe_snk_send_sink_alert_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + pd_dpm_send_alert(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +/* + * [PD3.0] Figure 8-77 Sink Port Get Source Capabilities Extended State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE +void pe_snk_get_source_cap_ext_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SOURCE_CAP_EXT); +} + +void pe_snk_get_source_cap_ext_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_source_cap_ext(pd_port); +} + +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ + +/* + * [PD3.0] Figure 8-79 Sink Port Get Source Status State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE +void pe_snk_get_source_status_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_STATUS); +} + +void pe_snk_get_source_status_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_status(pd_port); +} + +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ + +/* + * [PD3.0] Figure 8-82 Sink Give Sink Status State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL +void pe_snk_give_sink_status_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_status(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + +/* + * [PD3.0] Figure 8-83 Sink Port Get Source PPS Status State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +void pe_snk_get_pps_status_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_PPS_STATUS); +} + +void pe_snk_get_pps_status_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_pps_status(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +#endif /* CONFIG_USB_PD_REV30 */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_src.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_src.c new file mode 100644 index 000000000000..ed81a0f2f813 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_src.c @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for SRC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +/* + * [PD2.0] Figure 8-38 Source Port Policy Engine state diagram + */ + +void pe_src_startup_entry(struct pd_port *pd_port) +{ + pd_reset_protocol_layer(pd_port, false); + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_STARTUP); + + /* + * When entering this state, + * VBUS must be valid even for cc_attached. + */ + + pd_enable_timer(pd_port, PD_TIMER_SOURCE_START); +} + +void pe_src_discovery_entry(struct pd_port *pd_port) +{ + /* The SourceCapabilitiesTimer continues to run during the states + * defined in Source Startup Structured VDM Discover Identity State + * Diagram + */ + + pd_port->pe_data.pd_connected = false; + + pd_enable_timer(pd_port, PD_TIMER_SOURCE_CAPABILITY); + +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID + if (pd_is_discover_cable(pd_port)) + pd_enable_timer(pd_port, PD_TIMER_DISCOVER_ID); +#endif +} + +void pe_src_send_capabilities_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG_HRESET_IF_TOUT(pd_port); + + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_SEND_WAIT_CAP); + + pd_dpm_send_source_caps(pd_port); + pd_port->pe_data.cap_counter++; +} + +void pe_src_negotiate_capabilities_entry( + struct pd_port *pd_port) +{ + pd_handle_first_pd_command(pd_port); + pd_dpm_src_evaluate_request(pd_port); +} + +void pe_src_transition_supply_entry(struct pd_port *pd_port) +{ + uint8_t msg = PD_CTRL_ACCEPT; + struct pd_event *pd_event = pd_get_curr_pd_event(pd_port); + + /* goto-min */ + if (pd_event->event_type == PD_EVT_TCP_MSG) { + msg = PD_CTRL_GOTO_MIN; + pd_port->request_i_new = pd_port->request_i_op; + } + + pd_send_sop_ctrl_msg(pd_port, msg); +} + +void pe_src_transition_supply2_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_PS_RDY); +} + +void pe_src_ready_entry(struct pd_port *pd_port) +{ + pd_notify_pe_src_explicit_contract(pd_port); + pe_power_ready_entry(pd_port); +} + +void pe_src_disabled_entry(struct pd_port *pd_port) +{ + pd_notify_pe_hard_reset_completed(pd_port); + + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_DISABLE); + pd_update_connect_state(pd_port, PD_CONNECT_TYPEC_ONLY_SRC); + pd_dpm_dynamic_disable_vconn(pd_port); +} + +void pe_src_capability_response_entry(struct pd_port *pd_port) +{ + pd_reply_wait_reject_msg_no_resp(pd_port); +} + +void pe_src_hard_reset_entry(struct pd_port *pd_port) +{ + pd_send_hard_reset(pd_port); + pd_enable_timer(pd_port, PD_TIMER_PS_HARD_RESET); +} + +void pe_src_hard_reset_received_entry(struct pd_port *pd_port) +{ + pd_enable_timer(pd_port, PD_TIMER_PS_HARD_RESET); +} + +void pe_src_transition_to_default_entry(struct pd_port *pd_port) +{ + pd_reset_local_hw(pd_port); + pd_dpm_src_hard_reset(pd_port); +} + +void pe_src_transition_to_default_exit(struct pd_port *pd_port) +{ + pd_set_vconn(pd_port, PD_ROLE_VCONN_ON); + pd_enable_timer(pd_port, PD_TIMER_NO_RESPONSE); +} + +void pe_src_get_sink_cap_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SINK_CAP); +} + +void pe_src_get_sink_cap_exit(struct pd_port *pd_port) +{ + pd_dpm_dr_inform_sink_cap(pd_port); +} + +void pe_src_wait_new_capabilities_entry( + struct pd_port *pd_port) +{ + /* Wait for new Source Capabilities */ +} + +void pe_src_send_soft_reset_entry(struct pd_port *pd_port) +{ + pd_send_soft_reset(pd_port); +} + +void pe_src_soft_reset_entry(struct pd_port *pd_port) +{ + pd_handle_soft_reset(pd_port); +} + +/* + * [PD2.0] Figure 8-81 + Source Startup Structured VDM Discover Identity State Diagram (TODO) + */ + +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID + +#ifdef CONFIG_PD_SRC_RESET_CABLE +void pe_src_cbl_send_soft_reset_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_RESPONSE(pd_port); + + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_DISCOVER_CABLE); + + pd_send_cable_soft_reset(pd_port); +} +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + +void pe_src_vdm_identity_request_entry(struct pd_port *pd_port) +{ + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_DISCOVER_CABLE); + + pd_port->pe_data.discover_id_counter++; + pd_send_vdm_discover_id(pd_port, TCPC_TX_SOP_PRIME); +} + +void pe_src_vdm_identity_acked_entry(struct pd_port *pd_port) +{ + pd_dpm_inform_cable_id(pd_port, true); +} + +void pe_src_vdm_identity_naked_entry(struct pd_port *pd_port) +{ + pd_dpm_inform_cable_id(pd_port, true); +} + +#endif /* CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID */ + + +#ifdef CONFIG_USB_PD_REV30 + +/* + * [PD3.0] Source Port Not Supported Message State Diagram + */ + +void pe_src_send_not_supported_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_NOT_SUPPORTED); +} + +void pe_src_not_supported_received_entry(struct pd_port *pd_port) +{ + PE_STATE_DPM_INFORMED(pd_port); + + pd_dpm_inform_not_support(pd_port); +} + +void pe_src_chunk_received_entry(struct pd_port *pd_port) +{ + pd_enable_timer(pd_port, PD_TIMER_CK_NOT_SUPPORTED); +} + +/* + * [PD3.0] Figure 8-73 Source Port Source Alert State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL +void pe_src_send_source_alert_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + pd_dpm_send_alert(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +/* + * [PD3.0] Figure 8-76 Source Port Sink Alert State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +void pe_src_sink_alert_received_entry(struct pd_port *pd_port) +{ + PE_STATE_DPM_INFORMED(pd_port); + + pd_dpm_inform_alert(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +/* + * [PD3.0] Figure 8-78 Source Give Source Capabilities Extended State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL +void pe_src_give_source_cap_ext_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_source_cap_ext(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + +/* + * [PD3.0] Figure 8-80 Source Give Source Status State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL +void pe_src_give_source_status_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + pd_dpm_send_status(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + +/* + * [PD3.0] Figure 8-81 Source Port Get Sink Status State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE +void pe_src_get_sink_status_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_MSG(pd_port); + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_STATUS); +} + +void pe_src_get_sink_status_exit(struct pd_port *pd_port) +{ + pd_dpm_inform_status(pd_port); +} +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ + +/* + * [PD3.0] Figure 8-84 Source Give Source PPS Status State Diagram + */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE +void pe_src_give_pps_status_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + + /* TODO */ + PD_BUG_ON(1); +} +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ + +#endif /* CONFIG_USB_PD_REV30 */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_ufp.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_ufp.c new file mode 100644 index 000000000000..d87f7acee0ee --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_ufp.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for UFP + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +/* + * [PD2.0] Figure 8-58 UFP Structured VDM Discover Identity State Diagram + */ + +void pe_ufp_vdm_get_identity_entry(struct pd_port *pd_port) +{ + pd_dpm_ufp_request_id_info(pd_port); +} + +/* + * [PD2.0] Figure 8-59 UFP Structured VDM Discover SVIDs State Diagram + */ + +void pe_ufp_vdm_get_svids_entry(struct pd_port *pd_port) +{ + pd_dpm_ufp_request_svid_info(pd_port); +} + +/* + * [PD2.0] Figure 8-60 UFP Structured VDM Discover Modes State Diagram + */ + +void pe_ufp_vdm_get_modes_entry(struct pd_port *pd_port) +{ + pd_dpm_ufp_request_mode_info(pd_port); +} + +/* + * [PD2.0] Figure 8-61 UFP Structured VDM Enter Mode State Diagram + */ + +void pe_ufp_vdm_evaluate_mode_entry_entry( + struct pd_port *pd_port) +{ + pd_dpm_ufp_request_enter_mode(pd_port); +} + +/* + * [PD2.0] Figure 8-62 UFP Structured VDM Exit Mode State Diagram + */ + +void pe_ufp_vdm_mode_exit_entry(struct pd_port *pd_port) +{ + pd_dpm_ufp_request_exit_mode(pd_port); +} + +/* + * [PD2.0] Figure 8-63 UFP VDM Attention State Diagram + */ + +void pe_ufp_vdm_attention_request_entry( + struct pd_port *pd_port) +{ + VDM_STATE_NORESP_CMD(pd_port); + + switch (pd_port->mode_svid) { +#ifdef CONFIG_USB_PD_ALT_MODE + case USB_SID_DISPLAYPORT: + pd_dpm_ufp_send_dp_attention(pd_port); + break; +#endif + default: + pd_send_vdm_attention(pd_port, + TCPC_TX_SOP, pd_port->mode_svid, pd_port->mode_obj_pos); + break; + } +} + +/* + * ALT Mode + */ + +#ifdef CONFIG_USB_PD_ALT_MODE + +void pe_ufp_vdm_dp_status_update_entry(struct pd_port *pd_port) +{ + pd_dpm_ufp_request_dp_status(pd_port); +} + +void pe_ufp_vdm_dp_configure_entry(struct pd_port *pd_port) +{ + pd_dpm_ufp_request_dp_config(pd_port); +} + +#endif /* CONFIG_USB_PD_ALT_MODE */ + +/* + * SVMD/UVDM + */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + +void pe_ufp_uvdm_recv_entry(struct pd_port *pd_port) +{ + pd_dpm_ufp_recv_uvdm(pd_port); +} + +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_policy_engine_vcs.c b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_vcs.c new file mode 100644 index 000000000000..525183069346 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_policy_engine_vcs.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for VCS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" + +/* + * [PD2.0] Figure 8-57 VCONN Swap State Diagram + */ + +void pe_vcs_send_swap_entry(struct pd_port *pd_port) +{ + pe_send_swap_request_entry(pd_port, PD_CTRL_VCONN_SWAP); +} + +void pe_vcs_evaluate_swap_entry(struct pd_port *pd_port) +{ + pd_dpm_vcs_evaluate_swap(pd_port); +} + +void pe_vcs_accept_swap_entry(struct pd_port *pd_port) +{ + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT); +} + +void pe_vcs_reject_vconn_swap_entry(struct pd_port *pd_port) +{ + pd_reply_wait_reject_msg(pd_port); +} + +void pe_vcs_wait_for_vconn_entry(struct pd_port *pd_port) +{ + pd_enable_pe_state_timer(pd_port, PD_TIMER_VCONN_ON); +} + +void pe_vcs_turn_off_vconn_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_DPM_ACK(pd_port); + pd_dpm_vcs_enable_vconn(pd_port, PD_ROLE_VCONN_OFF); +} + +void pe_vcs_turn_on_vconn_entry(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_RESET_CABLE + dpm_reaction_set(pd_port, DPM_REACTION_CAP_RESET_CABLE); +#endif /* CONFIG_USB_PD_RESET_CABLE */ +#endif /* CONFIG_USB_PD_REV30 */ + + pd_dpm_vcs_enable_vconn(pd_port, PD_ROLE_VCONN_DYNAMIC_ON); +} + +void pe_vcs_send_ps_rdy_entry(struct pd_port *pd_port) +{ + PE_STATE_WAIT_TX_SUCCESS(pd_port); + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_PS_RDY); +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt.c new file mode 100644 index 000000000000..929da24140e6 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt.c @@ -0,0 +1,837 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" +#include "inc/pd_dpm_core.h" + +/* + * [BLOCK] print event + */ + +#if PE_EVENT_DBG_ENABLE +static const char * const pd_ctrl_msg_name[] = { + "ctrl0", + "good_crc", + "goto_min", + "accept", + "reject", + "ping", + "ps_rdy", + "get_src_cap", + "get_snk_cap", + "dr_swap", + "pr_swap", + "vs_swap", + "wait", + "soft_reset", + "ctrlE", + "ctrlF", +#ifdef CONFIG_USB_PD_REV30 + "no_support", + "get_src_cap_ex", + "get_status", + "fr_swap", + "get_pps", + "get_cc", +#endif /* CONFIG_USB_PD_REV30 */ +}; + +static inline void print_ctrl_msg_event(struct tcpc_device *tcpc, uint8_t msg) +{ + if (msg < PD_CTRL_MSG_NR) + PE_EVT_INFO("%s\n", pd_ctrl_msg_name[msg]); +} + +static const char * const pd_data_msg_name[] = { + "data0", + "src_cap", + "request", + "bist", + "sink_cap", +#ifdef CONFIG_USB_PD_REV30 + "bat_status", + "alert", + "get_ci", +#else + "data5", + "data6", + "data7", +#endif /* CONFIG_USB_PD_REV30 */ + "data8", + "data9", + "dataA", + "dataB", + "dataC", + "dataD", + "dataE", + "vdm", +}; + +static inline void print_data_msg_event(struct tcpc_device *tcpc, uint8_t msg) +{ + if (msg < PD_DATA_MSG_NR) + PE_EVT_INFO("%s\n", pd_data_msg_name[msg]); +} + +#ifdef CONFIG_USB_PD_REV30 + +static const char *const pd_ext_msg_name[] = { + "ext0", + "src_cap_ex", + "status", + "get_bat_cap", + "get_bat_status", + "bat_cap", + "get_mfr_info", + "mfr_info", + "sec_req", + "sec_resp", + "fw_update_req", + "fw_update_res", + "pps_status", + "ci", + "cc", +}; + +static inline void print_ext_msg_event(struct tcpc_device *tcpc, uint8_t msg) +{ + if (msg < PD_EXT_MSG_NR) + PE_EVT_INFO("%s\n", pd_ext_msg_name[msg]); +} + +#endif /* CONFIG_USB_PD_REV30 */ + +static const char *const pd_hw_msg_name[] = { + "Detached", + "Attached", + "hard_reset", + "vbus_high", + "vbus_low", + "vbus_0v", + "vbus_stable", + "tx_err", + "discard", + "retry_vdm", + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + "sink_tx_change", +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +}; + +static inline void print_hw_msg_event(struct tcpc_device *tcpc, uint8_t msg) +{ + if (msg < PD_HW_MSG_NR) + PE_EVT_INFO("%s\n", pd_hw_msg_name[msg]); +} + +static const char *const pd_pe_msg_name[] = { + "reset_prl_done", + "pr_at_dft", + "hard_reset_done", + "pe_idle", + "vdm_reset", + "vdm_not_support", +}; + +static inline void print_pe_msg_event(struct tcpc_device *tcpc, uint8_t msg) +{ + if (msg < PD_PE_MSG_NR) + PE_EVT_INFO("%s\n", pd_pe_msg_name[msg]); +} + +static const char * const pd_dpm_msg_name[] = { + "ack", + "nak", + "cap_change", + "not_support", +}; + +static inline void print_dpm_msg_event(struct tcpc_device *tcpc, uint8_t msg) +{ + if (msg < PD_DPM_MSG_NR) + PE_EVT_INFO("dpm_%s\n", pd_dpm_msg_name[msg]); +} + +static const char *const tcp_dpm_evt_name[] = { + /* TCP_DPM_EVT_UNKONW */ + "unknown", + + /* TCP_DPM_EVT_PD_COMMAND */ + "pr_swap_snk", + "pr_swap_src", + "dr_swap_ufp", + "dr_swap_dfp", + "vc_swap_off", + "vc_swap_on", + "goto_min", + "soft_reset", + "cable_soft_reset", + "get_src_cap", + "get_snk_cap", + "request", + "request_ex", + "request_again", + "bist_cm2", + "dummy", + +#ifdef CONFIG_USB_PD_REV30 + "get_src_cap_ext", + "get_status", + "fr_swap_snk", + "fr_swap_src", + "get_cc", + "get_pps", + + "alert", + "get_ci", + "get_bat_cap", + "get_bat_status", + "get_mfrs_info", +#endif /* CONFIG_USB_PD_REV30 */ + + /* TCP_DPM_EVT_VDM_COMMAND */ + "disc_cable", + "disc_id", + "disc_svid", + "disc_mode", + "enter_mode", + "exit_mode", + "attention", + +#ifdef CONFIG_USB_PD_ALT_MODE + "dp_atten", +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + "dp_status", + "dp_config", +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + "uvdm", +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + + /* TCP_DPM_EVT_IMMEDIATELY */ + "hard_reset", + "error_recovery", +}; + +static inline void print_tcp_event(struct tcpc_device *tcpc, uint8_t msg) +{ + if (msg < TCP_DPM_EVT_NR) + PE_EVT_INFO("tcp_event(%s), %d\n", + tcp_dpm_evt_name[msg], msg); +} +#endif + +static inline void print_event( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#if PE_EVENT_DBG_ENABLE + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + switch (pd_event->event_type) { + case PD_EVT_CTRL_MSG: + print_ctrl_msg_event(tcpc, pd_event->msg); + break; + + case PD_EVT_DATA_MSG: + print_data_msg_event(tcpc, pd_event->msg); + break; + +#ifdef CONFIG_USB_PD_REV30 + case PD_EVT_EXT_MSG: + print_ext_msg_event(tcpc, pd_event->msg); + break; +#endif /* CONFIG_USB_PD_REV30 */ + + case PD_EVT_DPM_MSG: + print_dpm_msg_event(tcpc, pd_event->msg); + break; + + case PD_EVT_HW_MSG: + print_hw_msg_event(tcpc, pd_event->msg); + break; + + case PD_EVT_PE_MSG: + print_pe_msg_event(tcpc, pd_event->msg); + break; + + case PD_EVT_TIMER_MSG: + PE_EVT_INFO("timer\n"); + break; + + case PD_EVT_TCP_MSG: + print_tcp_event(tcpc, pd_event->msg); + break; + } +#endif +} + +/*---------------------------------------------------------------------------*/ + +bool pd_make_pe_state_transit(struct pd_port *pd_port, + uint8_t curr_state, const struct pe_state_reaction *state_reaction) +{ + int i; + const struct pe_state_transition *state_transition = + state_reaction->state_transition; + + for (i = 0; i < state_reaction->nr_transition; i++) { + if (state_transition[i].curr_state == curr_state) { + PE_TRANSIT_STATE(pd_port, + state_transition[i].next_state); + return true; + } + } + + return false; +} + +/*---------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_PD_REV30 +static inline bool pd30_process_ready_protocol_error(struct pd_port *pd_port) +{ + bool multi_chunk; + + if (!pd_check_rev30(pd_port)) + return false; + + if (!pd_port->curr_unsupported_msg) { + pe_transit_soft_reset_state(pd_port); + return true; + } + + multi_chunk = pd_is_multi_chunk_msg(pd_port); + + if (pd_port->power_role == PD_ROLE_SINK) { + PE_TRANSIT_STATE(pd_port, multi_chunk ? + PE_SNK_CHUNK_RECEIVED : PE_SNK_SEND_NOT_SUPPORTED); + return true; + } + + PE_TRANSIT_STATE(pd_port, multi_chunk ? + PE_SRC_CHUNK_RECEIVED : PE_SRC_SEND_NOT_SUPPORTED); + return true; +} +#endif /* CONFIG_USB_PD_REV30 */ + + +bool pd_process_protocol_error( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool power_change = false; +#if PE_INFO_ENABLE + uint8_t event_type = pd_event->event_type; + uint8_t msg_id = pd_get_msg_hdr_id(pd_port); + uint8_t msg_type = pd_event->msg; +#endif + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + switch (pd_port->pe_state_curr) { + case PE_SNK_TRANSITION_SINK: + /* fall through */ + case PE_SRC_TRANSITION_SUPPLY: /* never recv ping for Source =.=*/ + /* fall through */ + case PE_SRC_TRANSITION_SUPPLY2: + power_change = true; + if (pd_event_msg_match(pd_event, + PD_EVT_CTRL_MSG, PD_CTRL_PING)) { + PE_DBG("Ignore Ping\n"); + return false; + } + break; + +#ifdef CONFIG_USB_PD_PR_SWAP + case PE_PRS_SRC_SNK_WAIT_SOURCE_ON: +#endif /* CONFIG_USB_PD_PR_SWAP */ + if (pd_event_msg_match(pd_event, + PD_EVT_CTRL_MSG, PD_CTRL_PING)) { + PE_DBG("Ignore Ping\n"); + return false; + } + break; + +#ifdef CONFIG_USB_PD_REV30 + case PE_SNK_READY: + case PE_SRC_READY: + if (pd30_process_ready_protocol_error(pd_port)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30 */ + }; + + if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_IGNORE_UNKNOWN_EVENT) { + PE_DBG("Ignore Unknown Event\n"); + return false; + } + + if (pd_check_pe_during_hard_reset(pd_port)) { + PE_DBG("Ignore Event during HReset\n"); + return false; + } + + /* + * msg_type: PD_EVT_CTRL_MSG (1), PD_EVT_DATA_MSG (2) + */ + + PE_INFO("PRL_ERR: %d-%d-%d\n", event_type, msg_type, msg_id); + + if (pd_port->pe_data.during_swap) { +#ifdef CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY + PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY); +#else + pe_transit_hard_reset_state(pd_port); +#endif + } else if (power_change) + pe_transit_hard_reset_state(pd_port); + else + pe_transit_soft_reset_state(pd_port); + + return true; +} + +bool pd_process_tx_failed(struct pd_port *pd_port) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_check_pe_state_ready(pd_port) || + pd_check_pe_during_hard_reset(pd_port)) { + PE_DBG("Ignore tx_failed\n"); + return false; + } + + pe_transit_soft_reset_state(pd_port); + return true; +} + +/*---------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_PD_RESET_CABLE +static inline bool pd_process_cable_ctrl_msg_accept( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_port->pe_state_curr) { +#ifdef CONFIG_PD_SRC_RESET_CABLE + case PE_SRC_CBL_SEND_SOFT_RESET: + vdm_put_dpm_discover_cable_event(pd_port); + return false; +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + +#ifdef CONFIG_PD_DFP_RESET_CABLE + case PE_DFP_CBL_SEND_SOFT_RESET: + pe_transit_ready_state(pd_port); + return true; +#endif /* CONFIG_PD_DFP_RESET_CABLE */ + } + + return false; +} +#endif /* CONFIG_USB_PD_RESET_CABLE */ + +static inline bool pd_process_event_cable( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool ret = false; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + +#ifdef CONFIG_USB_PD_RESET_CABLE + if (pd_event->msg == PD_CTRL_ACCEPT) + ret = pd_process_cable_ctrl_msg_accept(pd_port, pd_event); +#endif /* CONFIG_USB_PD_RESET_CABLE */ + + if (!ret) + PE_DBG("Ignore not SOP Ctrl Msg\n"); + + return ret; +} + +/*---------------------------------------------------------------------------*/ + +static void pd_copy_msg_data(struct pd_port *pd_port, + uint8_t *payload, uint16_t count, uint8_t unit_sz) +{ + pd_port->pd_msg_data_size = count * unit_sz; + pd_port->pd_msg_data_count = count; + pd_port->pd_msg_data_payload = payload; +} + +#ifdef CONFIG_USB_PD_REV30 +static inline void pd_copy_msg_data_from_ext_evt( + struct pd_port *pd_port, struct pd_msg *pd_msg) +{ + uint32_t *payload = pd_msg->payload; + + uint16_t *ext_hdr = (uint16_t *) payload; + uint8_t *ext_data = (uint8_t *) (ext_hdr+1); + + uint16_t size = PD_EXT_HEADER_DATA_SIZE(*ext_hdr); + + pd_copy_msg_data(pd_port, ext_data, size, 1); +} +#endif /* CONFIG_USB_PD_REV30 */ + +static inline void pd_copy_msg_data_from_evt( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + struct pd_msg *pd_msg = pd_event->pd_msg; + + switch (pd_event->event_type) { + case PD_EVT_DATA_MSG: + PD_BUG_ON(pd_msg == NULL); + pd_copy_msg_data(pd_port, (uint8_t *)pd_msg->payload, + pd_get_msg_hdr_cnt(pd_port), sizeof(uint32_t)); + break; + +#ifdef CONFIG_USB_PD_REV30 + case PD_EVT_EXT_MSG: + PD_BUG_ON(pd_msg == NULL); + pd_copy_msg_data_from_ext_evt(pd_port, pd_msg); + return; +#endif /* CONFIG_USB_PD_REV30 */ + + default: + pd_copy_msg_data(pd_port, NULL, 0, 0); + } + +} + +/*---------------------------------------------------------------------------*/ + +/* + * + * @ true : valid message + * @ false : invalid message, pe should drop the message + */ + +static inline bool pe_is_valid_pd_msg_id(struct pd_port *pd_port, + struct pd_event *pd_event, struct pd_msg *pd_msg) +{ + uint8_t sop_type = pd_msg->frame_type; + uint8_t msg_id = pd_get_msg_hdr_id(pd_port); + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->pe_state_curr == PE_BIST_TEST_DATA) + return false; + + if (pd_event->event_type == PD_EVT_CTRL_MSG) { + switch (pd_event->msg) { + /* SofReset always has a MessageID value of zero */ + case PD_CTRL_SOFT_RESET: + if (msg_id != 0) { + PE_INFO("Repeat soft_reset\n"); + return false; + } + return true; + + case PD_CTRL_GOOD_CRC: + PE_DBG("Discard_CRC\n"); + return true; + +#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP + case PD_CTRL_PS_RDY: + if (pd_port->msg_id_pr_swap_last == msg_id) { + PE_INFO("Repeat ps_rdy\n"); + return false; + } + break; +#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */ + } + } + + if (pd_port->pe_data.msg_id_rx[sop_type] == msg_id) { + PE_INFO("Repeat msg: %c:%d:%d\n", + (pd_event->event_type == PD_EVT_CTRL_MSG) ? 'C' : 'D', + pd_event->msg, msg_id); + return false; + } + + pd_port->pe_data.msg_id_rx[sop_type] = msg_id; + return true; +} + +static inline bool pe_is_valid_pd_msg_role(struct pd_port *pd_port, + struct pd_event *pd_event, struct pd_msg *pd_msg) +{ + bool ret = true; + uint8_t msg_pr, msg_dr; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_msg == NULL) /* Good-CRC */ + return true; + + if (pd_msg->frame_type != TCPC_TX_SOP) + return true; + + msg_pr = PD_HEADER_PR(pd_msg->msg_hdr); + msg_dr = PD_HEADER_DR(pd_msg->msg_hdr); + + /* + * The Port Power Role field of a received Message shall not be verified + * by the receiver and no error recovery action shall be + * taken if it is incorrect. + */ + + if (msg_pr == pd_port->power_role) + PE_DBG("Wrong PR:%d\n", msg_pr); + + /* + * Should a Type-C Port receive a Message with the Port Data Role field + * set to the same Data Role as its current Data Role, + * except for the GoodCRC Message, + * Type-C error recovery actions as defined + * in [USBType-C 1.0] shall be performed. + */ + + if (msg_dr == pd_port->data_role) { +#ifdef CONFIG_USB_PD_CHECK_DATA_ROLE + ret = false; +#endif + PE_INFO("Wrong DR:%d\n", msg_dr); + } + + return ret; +} + +static inline void pe_translate_pd_msg_event(struct pd_port *pd_port, + struct pd_event *pd_event, struct pd_msg *pd_msg) +{ + uint16_t msg_hdr; + + PD_BUG_ON(pd_msg == NULL); + + msg_hdr = pd_msg->msg_hdr; + pd_port->curr_msg_hdr = msg_hdr; + pd_event->msg = PD_HEADER_TYPE(msg_hdr); + + if (PD_HEADER_CNT(msg_hdr)) + pd_event->event_type = PD_EVT_DATA_MSG; + else + pd_event->event_type = PD_EVT_CTRL_MSG; + +#ifdef CONFIG_USB_PD_REV30 + if (PD_HEADER_EXT(msg_hdr)) + pd_event->event_type = PD_EVT_EXT_MSG; + + if (pd_msg->frame_type == TCPC_TX_SOP_PRIME) { + pd_sync_sop_prime_spec_revision( + pd_port, PD_HEADER_REV(msg_hdr)); + } +#endif /* CONFIG_USB_PD_REV30 */ +} + +static inline uint8_t pe_get_startup_state( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool act_as_sink = true; + uint8_t startup_state = 0xff; + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + pd_port->custom_dbgacc = false; +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + + switch (pd_event->msg_sec) { + case TYPEC_ATTACHED_DBGACC_SNK: +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + pd_port->custom_dbgacc = true; + startup_state = PE_DBG_READY; + break; +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + case TYPEC_ATTACHED_SNK: + startup_state = PE_SNK_STARTUP; + break; + + case TYPEC_ATTACHED_SRC: + act_as_sink = false; + startup_state = PE_SRC_STARTUP; + break; + } + + /* At least > 2 for Ellisys VNDI PR_SWAP */ +#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE + if (pd_port->error_recovery_once > 2) + startup_state = PE_ERROR_RECOVERY_ONCE; +#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */ + + pd_init_message_hdr(pd_port, act_as_sink); + return startup_state; +} + +static inline bool pe_transit_startup_state( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + uint8_t startup_state = + pe_get_startup_state(pd_port, pd_event); + + if (startup_state == 0xff) + return false; + + pd_dpm_notify_pe_startup(pd_port); + PE_TRANSIT_STATE(pd_port, startup_state); + + return true; +} + +enum { + TII_TRAP_IN_IDLE = 0, + TII_TRANSIT_STATE = 1, + TII_PE_RUNNING = 2, +}; + +static inline uint8_t pe_check_trap_in_idle_state( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + switch (pd_port->pe_pd_state) { + case PE_IDLE1: + case PE_ERROR_RECOVERY: + if (pd_event_pe_msg_match(pd_event, PD_PE_IDLE)) { + PE_TRANSIT_STATE(pd_port, PE_IDLE2); + return TII_TRANSIT_STATE; + } + + pd_try_put_pe_idle_event(pd_port); + break; + + case PE_IDLE2: + if (pd_event_hw_msg_match(pd_event, PD_HW_CC_ATTACHED)) { + if (pe_transit_startup_state(pd_port, pd_event)) + return TII_TRANSIT_STATE; + } + + /* The original IDLE2 may trigger by PE_IDLE_TOUT */ + if (pd_event_hw_msg_match(pd_event, PD_HW_CC_DETACHED)) + pd_notify_pe_idle(pd_port); + break; + + default: + if (pd_event_hw_msg_match(pd_event, PD_HW_CC_DETACHED)) { + PE_TRANSIT_STATE(pd_port, PE_IDLE1); + return TII_TRANSIT_STATE; + } + return TII_PE_RUNNING; + } + + PE_DBG("Trap in idle state, Ignore All MSG (%d:%d)\n", + pd_event->event_type, pd_event->msg); + return TII_TRAP_IN_IDLE; +} + +static inline void pe_init_curr_state(struct pd_port *pd_port) +{ + if (pd_port->power_role == PD_ROLE_SINK) { + pd_port->curr_ready_state = PE_SNK_READY; + pd_port->curr_hreset_state = PE_SNK_HARD_RESET; + pd_port->curr_sreset_state = PE_SNK_SEND_SOFT_RESET; + } else { + pd_port->curr_ready_state = PE_SRC_READY; + pd_port->curr_hreset_state = PE_SRC_HARD_RESET; + pd_port->curr_sreset_state = PE_SRC_SEND_SOFT_RESET; + } + + pd_port->curr_unsupported_msg = false; + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + if (pd_port->custom_dbgacc) + pd_port->curr_ready_state = PE_DBG_READY; +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ +} + +bool pd_process_event( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool ret = false; + struct pd_msg *pd_msg = pd_event->pd_msg; + uint8_t tii = pe_check_trap_in_idle_state(pd_port, pd_event); + + if (tii < TII_PE_RUNNING) + return tii; + + pe_init_curr_state(pd_port); + + if (pd_event->event_type == PD_EVT_PD_MSG) + pe_translate_pd_msg_event(pd_port, pd_event, pd_msg); + +#if PE_EVT_INFO_VDM_DIS + if (!pd_curr_is_vdm_evt(pd_port)) +#endif + print_event(pd_port, pd_event); + + if ((pd_event->event_type < PD_EVT_PD_MSG_END) && (pd_msg != NULL)) { + + if (!pe_is_valid_pd_msg_id(pd_port, pd_event, pd_msg)) + return false; + + if (!pe_is_valid_pd_msg_role(pd_port, pd_event, pd_msg)) { + PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY); + return true; + } + } + + pd_copy_msg_data_from_evt(pd_port, pd_event); + + if (pd_curr_is_vdm_evt(pd_port)) + return pd_process_event_vdm(pd_port, pd_event); + + if (pd_event->event_type == PD_EVT_TCP_MSG) + return pd_process_event_tcp(pd_port, pd_event); + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + if (pd_port->custom_dbgacc) + return pd_process_event_dbg(pd_port, pd_event); +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + + if ((pd_event->event_type == PD_EVT_CTRL_MSG) && + (pd_event->msg != PD_CTRL_GOOD_CRC) && + (pd_msg != NULL) && (pd_msg->frame_type != TCPC_TX_SOP)) + return pd_process_event_cable(pd_port, pd_event); + + if (pd_process_event_com(pd_port, pd_event)) + return true; + + switch (pd_port->state_machine) { +#ifdef CONFIG_USB_PD_DR_SWAP + case PE_STATE_MACHINE_DR_SWAP: + ret = pd_process_event_drs(pd_port, pd_event); + break; +#endif /* CONFIG_USB_PD_DR_SWAP */ + +#ifdef CONFIG_USB_PD_PR_SWAP + case PE_STATE_MACHINE_PR_SWAP: + ret = pd_process_event_prs(pd_port, pd_event); + break; +#endif /* CONFIG_USB_PD_PR_SWAP */ + +#ifdef CONFIG_USB_PD_VCONN_SWAP + case PE_STATE_MACHINE_VCONN_SWAP: + ret = pd_process_event_vcs(pd_port, pd_event); + break; +#endif /* CONFIG_USB_PD_VCONN_SWAP */ + } + + if (ret) + return true; + + if (pd_port->power_role == PD_ROLE_SINK) + ret = pd_process_event_snk(pd_port, pd_event); + else + ret = pd_process_event_src(pd_port, pd_event); + + return ret; +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_com.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_com.c new file mode 100644 index 000000000000..85fd086033d3 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_com.c @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Policy Engine for Common + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" +#include "inc/pd_dpm_core.h" + +/* + * [BLOCK] DRP (dr_swap, pr_swap, vconn_swap) + */ + +#ifdef CONFIG_USB_PD_DR_SWAP +static inline bool pd_evaluate_reject_dr_swap(struct pd_port *pd_port) +{ + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_DATA) { + if (pd_port->power_role == PD_ROLE_DFP) + return pd_port->dpm_caps & + DPM_CAP_DR_SWAP_REJECT_AS_UFP; + return pd_port->dpm_caps & DPM_CAP_DR_SWAP_REJECT_AS_DFP; + } + + return true; +} +#endif /* CONFIG_USB_PD_DR_SWAP */ + +#ifdef CONFIG_USB_PD_PR_SWAP +static inline bool pd_evaluate_reject_pr_swap(struct pd_port *pd_port) +{ + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) { + if (pd_port->power_role == PD_ROLE_SOURCE) + return pd_port->dpm_caps & + DPM_CAP_PR_SWAP_REJECT_AS_SNK; + return pd_port->dpm_caps & DPM_CAP_PR_SWAP_REJECT_AS_SRC; + } + + return true; +} +#endif /* CONFIG_USB_PD_PR_SWAP */ + +#ifdef CONFIG_USB_PD_VCONN_SWAP +static inline bool pd_evaluate_accept_vconn_swap(struct pd_port *pd_port) +{ + if (pd_port->dpm_caps & DPM_CAP_LOCAL_VCONN_SUPPLY) + return true; + + return false; +} +#endif /* CONFIG_USB_PD_VCONN_SWAP */ + +static inline bool pd_process_ctrl_msg_dr_swap( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + if (pd_port->pe_data.modal_operation) { + pe_transit_hard_reset_state(pd_port); + return true; + } + +#ifdef CONFIG_USB_PD_DR_SWAP + if (!pd_check_pe_state_ready(pd_port)) + return false; + + if (!pd_evaluate_reject_dr_swap(pd_port)) { + pd_port->pe_data.during_swap = false; + pd_port->state_machine = PE_STATE_MACHINE_DR_SWAP; + + PE_TRANSIT_DATA_STATE(pd_port, + PE_DRS_UFP_DFP_EVALUATE_DR_SWAP, + PE_DRS_DFP_UFP_EVALUATE_DR_SWAP); + return true; + } +#endif /* CONFIG_USB_PD_DR_SWAP */ + + PE_TRANSIT_STATE(pd_port, PE_REJECT); + return true; +} + +static inline bool pd_process_ctrl_msg_pr_swap( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#ifdef CONFIG_USB_PD_PR_SWAP + if (!pd_evaluate_reject_pr_swap(pd_port)) { + pd_port->pe_data.during_swap = false; + pd_port->state_machine = PE_STATE_MACHINE_PR_SWAP; + pe_transit_evaluate_pr_swap_state(pd_port); + return true; + } +#endif /* CONFIG_USB_PD_PR_SWAP */ + + PE_TRANSIT_STATE(pd_port, PE_REJECT); + return true; +} + +static inline bool pd_process_ctrl_msg_vconn_swap( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#ifdef CONFIG_USB_PD_VCONN_SWAP + if (!pd_check_pe_state_ready(pd_port)) + return false; + + if (pd_evaluate_accept_vconn_swap(pd_port)) { + pd_port->state_machine = PE_STATE_MACHINE_VCONN_SWAP; + PE_TRANSIT_STATE(pd_port, PE_VCS_EVALUATE_SWAP); + return true; + } +#endif /* CONFIG_USB_PD_VCONN_SWAP */ + + if (!pd_check_rev30(pd_port)) { + PE_TRANSIT_STATE(pd_port, PE_REJECT); + return true; + } + + return false; +} + +/* + * [BLOCK] BIST + */ + +static inline bool pd_process_data_msg_bist( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->request_v > 5000) { + PE_INFO("bist_not_vsafe5v\n"); + return false; + } + + switch (BDO_MODE(pd_event->pd_msg->payload[0])) { + case BDO_MODE_TEST_DATA: + PE_DBG("bist_test\n"); + PE_TRANSIT_STATE(pd_port, PE_BIST_TEST_DATA); + pd_noitfy_pe_bist_mode(pd_port, PD_BIST_MODE_TEST_DATA); + return true; + + case BDO_MODE_CARRIER2: + PE_DBG("bist_cm2\n"); + PE_TRANSIT_STATE(pd_port, PE_BIST_CARRIER_MODE_2); + pd_noitfy_pe_bist_mode(pd_port, PD_BIST_MODE_DISABLE); + return true; + + default: +#if 0 + case BDO_MODE_RECV: + case BDO_MODE_TRANSMIT: + case BDO_MODE_COUNTERS: + case BDO_MODE_CARRIER0: + case BDO_MODE_CARRIER1: + case BDO_MODE_CARRIER3: + case BDO_MODE_EYE: +#endif + PE_DBG("Unsupport BIST\n"); + pd_noitfy_pe_bist_mode(pd_port, PD_BIST_MODE_DISABLE); + return false; + } + + return false; +} + +/* + * [BLOCK] Porcess Ctrl MSG + */ + +static void pd_cancel_dpm_reaction(struct pd_port *pd_port) +{ + if (pd_port->pe_data.dpm_reaction_id < DPM_REACTION_REJECT_CANCEL) + dpm_reaction_clear(pd_port, pd_port->pe_data.dpm_reaction_id); +} + +static bool pd_process_ctrl_msg_wait_reject(struct pd_port *pd_port) +{ + pd_cancel_dpm_reaction(pd_port); + + if (pd_port->state_machine == PE_STATE_MACHINE_PR_SWAP) + pd_notify_pe_cancel_pr_swap(pd_port); + + pe_transit_ready_state(pd_port); + return true; +} + +static inline bool pd_process_ctrl_msg_wait(struct pd_port *pd_port) +{ + + return pd_process_ctrl_msg_wait_reject(pd_port); +} + +static inline bool pd_process_ctrl_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool ret = false; + +#ifdef CONFIG_USB_PD_REV30 + if (!pd_check_rev30(pd_port) && + pd_event->msg >= PD_CTRL_PD30_START) { + pd_event->msg = PD_CTRL_MSG_NR; + return false; + } +#endif /* CONFIG_USB_PD_REV30 */ + + switch (pd_event->msg) { + case PD_CTRL_GOOD_CRC: + if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER) + pd_enable_timer(pd_port, PD_TIMER_SENDER_RESPONSE); + + if (pd_port->pe_data.pe_state_flags2 & + PE_STATE_FLAG_BACK_READY_IF_RECV_GOOD_CRC) { + pe_transit_ready_state(pd_port); + return true; + } + break; + + case PD_CTRL_REJECT: + pd_notify_tcp_event_2nd_result(pd_port, TCP_DPM_RET_REJECT); + + if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_BACK_READY_IF_RECV_REJECT) { + return pd_process_ctrl_msg_wait_reject(pd_port); + } + break; + case PD_CTRL_WAIT: + pd_notify_tcp_event_2nd_result(pd_port, TCP_DPM_RET_WAIT); + + if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_BACK_READY_IF_RECV_WAIT) { + return pd_process_ctrl_msg_wait(pd_port); + } + break; + + case PD_CTRL_SOFT_RESET: + if (!pd_port->pe_data.during_swap && + !pd_check_pe_during_hard_reset(pd_port)) { + pe_transit_soft_reset_recv_state(pd_port); + return true; + } + break; + + /* Swap */ + case PD_CTRL_DR_SWAP: + ret = pd_process_ctrl_msg_dr_swap(pd_port, pd_event); + break; + + case PD_CTRL_PR_SWAP: + ret = pd_process_ctrl_msg_pr_swap(pd_port, pd_event); + break; + + case PD_CTRL_VCONN_SWAP: + ret = pd_process_ctrl_msg_vconn_swap(pd_port, pd_event); + break; + +#ifdef CONFIG_USB_PD_REV30 + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL + case PD_CTRL_GET_COUNTRY_CODE: + if (pd_port->country_nr) { + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + pe_get_curr_ready_state(pd_port), + PE_GIVE_COUNTRY_CODES); + } + break; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ + + case PD_CTRL_NOT_SUPPORTED: + pd_cancel_dpm_reaction(pd_port); + pd_notify_tcp_event_2nd_result( + pd_port, TCP_DPM_RET_NOT_SUPPORT); + + if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT) { + pe_transit_ready_state(pd_port); + return true; + } else if (pd_port->pe_data.vdm_state_timer) { + vdm_put_pe_event( + pd_port->tcpc, PD_PE_VDM_NOT_SUPPORT); + } + break; +#endif /* CONFIG_USB_PD_REV30 */ + } + + return ret; +} + +/* + * [BLOCK] Porcess Data MSG + */ + +static inline bool pd_process_data_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool ret = false; + uint8_t ready_state = pe_get_curr_ready_state(pd_port); + +#ifdef CONFIG_USB_PD_REV30 + if (!pd_check_rev30(pd_port) && + pd_event->msg >= PD_DATA_PD30_START) { + pd_event->msg = PD_DATA_MSG_NR; + return false; + } +#endif /* CONFIG_USB_PD_REV30 */ + + switch (pd_event->msg) { + case PD_DATA_BIST: + if (pd_port->pe_state_curr == ready_state) + ret = pd_process_data_msg_bist(pd_port, pd_event); + break; + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE + case PD_DATA_BAT_STATUS: + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + PE_GET_BATTERY_STATUS, ready_state); + break; +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL + case PD_DATA_GET_COUNTRY_INFO: + if (pd_port->country_nr) { + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + ready_state, PE_GIVE_COUNTRY_INFO); + } + break; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */ +#endif /* CONFIG_USB_PD_REV30 */ + } + + return ret; +} + +/* + * [BLOCK] Porcess Extend MSG + */ + +#ifdef CONFIG_USB_PD_REV30 +static inline bool pd_process_ext_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool ret = false; + uint8_t ready_state = pe_get_curr_ready_state(pd_port); + + if (!pd_check_rev30(pd_port)) { + pd_event->msg = PD_DATA_MSG_NR; + return false; + } + +#ifndef CONFIG_USB_PD_REV30_CHUNKING_BY_PE + if (pd_port->pe_state_curr == ready_state && + pd_is_multi_chunk_msg(pd_port)) { + pd_port->curr_unsupported_msg = true; + return pd_process_protocol_error(pd_port, pd_event); + } +#endif /* CONFIG_USB_PD_REV30_CHUNKING_BY_PE */ + + switch (pd_event->msg) { + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL + case PD_EXT_GET_BAT_CAP: + if (pd_port->bat_nr) { + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + ready_state, PE_GIVE_BATTERY_CAP); + } + break; +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL + case PD_EXT_GET_BAT_STATUS: + if (pd_port->bat_nr) { + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + ready_state, PE_GIVE_BATTERY_STATUS); + } + break; +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE + case PD_EXT_BAT_CAP: + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + PE_GET_BATTERY_CAP, ready_state); + break; +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL + case PD_EXT_GET_MFR_INFO: + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + ready_state, PE_GIVE_MANUFACTURER_INFO); + break; +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + case PD_EXT_MFR_INFO: + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + PE_GET_MANUFACTURER_INFO, ready_state); + break; +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE + case PD_EXT_COUNTRY_INFO: + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + PE_GET_COUNTRY_INFO, ready_state); + break; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE + case PD_EXT_COUNTRY_CODES: + ret = PE_MAKE_STATE_TRANSIT_SINGLE( + PE_GET_COUNTRY_CODES, ready_state); + break; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ + } + + return ret; +} +#endif /* CONFIG_USB_PD_REV30 */ + +/* + * [BLOCK] Porcess DPM MSG + */ + +static inline bool pd_process_dpm_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool ret = false; + + switch (pd_event->msg) { + case PD_DPM_ACK: + if (pd_port->pe_data.pe_state_flags2 & + PE_STATE_FLAG_BACK_READY_IF_DPM_ACK) { + pe_transit_ready_state(pd_port); + return true; + } + break; + +#ifdef CONFIG_USB_PD_REV30 + case PD_DPM_NOT_SUPPORT: + if (pd_check_rev30(pd_port)) { + PE_TRANSIT_STATE(pd_port, PE_VDM_NOT_SUPPORTED); + return true; + } + break; +#endif /* CONFIG_USB_PD_REV30 */ + } + + return ret; +} + +/* + * [BLOCK] Porcess HW MSG + */ + +static inline bool pd_process_recv_hard_reset( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER + if (pd_port->pe_data.recv_hard_reset_count > PD_HARD_RESET_COUNT) { + PE_TRANSIT_STATE(pd_port, PE_OVER_RECV_HRESET_LIMIT); + return true; + } + + pd_port->pe_data.recv_hard_reset_count++; +#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + +#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER + if (pd_check_pe_during_hard_reset(pd_port)) + pd_port->pe_data.renegotiation_count++; +#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */ + + pe_transit_hard_reset_recv_state(pd_port); + return true; +} + +static inline bool pd_process_hw_msg_tx_failed( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->pe_data.renegotiation_count > PD_HARD_RESET_COUNT) { + PE_INFO("renegotiation failed\n"); + PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY); + return true; + } +#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */ + + if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_BACK_READY_IF_TX_FAILED) { + pd_notify_tcp_event_2nd_result( + pd_port, TCP_DPM_RET_NO_RESPONSE); + pe_transit_ready_state(pd_port); + return true; + } else if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_HRESET_IF_TX_FAILED) { + pe_transit_hard_reset_state(pd_port); + return true; + } + + return false; +} + +static inline bool pd_process_hw_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_HW_RECV_HARD_RESET: + return pd_process_recv_hard_reset(pd_port, pd_event); + + case PD_HW_TX_FAILED: + return pd_process_hw_msg_tx_failed(pd_port, pd_event); + + default: + return false; + }; +} + +/* + * [BLOCK] Porcess Timer MSG + */ + +#ifdef CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT +static inline bool pd_check_rx_pending(struct pd_port *pd_port) +{ + uint32_t alert; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (tcpci_get_alert_status(tcpc, &alert)) + return false; + + if (alert & TCPC_REG_ALERT_RX_STATUS) { + PE_INFO("rx_pending\n"); +#ifndef CONFIG_USB_PD_ONLY_PRINT_SYSTEM_BUSY + pd_enable_timer(pd_port, PD_TIMER_SENDER_RESPONSE); +#endif + return true; + } + + return false; +} +#endif /* CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT */ + +static inline bool pd_process_timer_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + uint8_t ready_state = pe_get_curr_ready_state(pd_port); + + switch (pd_event->msg) { +#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT + case PD_TIMER_SENDER_RESPONSE: + +#ifdef CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT +#ifndef CONFIG_USB_PD_ONLY_PRINT_SYSTEM_BUSY + if (pd_check_rx_pending(pd_port)) + return false; +#else + pd_check_rx_pending(pd_port); +#endif /* CONFIG_USB_PD_PRINT_SYSTEM_BUSY */ +#endif /* CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT */ + + + pd_cancel_dpm_reaction(pd_port); + pd_notify_pe_cancel_pr_swap(pd_port); + pd_notify_tcp_event_2nd_result(pd_port, TCP_DPM_RET_TIMEOUT); + if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT) { + PE_TRANSIT_STATE(pd_port, ready_state); + return true; + } + + if (pd_port->pe_data.pe_state_flags & + PE_STATE_FLAG_HRESET_IF_SR_TIMEOUT) { + pe_transit_hard_reset_state(pd_port); + return true; + } + break; +#endif /* CONFIG_USB_PD_DBG_IGRONE_TIMEOUT */ + case PD_TIMER_BIST_CONT_MODE: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_BIST_CARRIER_MODE_2, ready_state)) + return true; + break; + +#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY + case PD_TIMER_DFP_FLOW_DELAY: + if (pd_port->pe_state_curr == ready_state && + pd_port->data_role == PD_ROLE_DFP) { + dpm_reaction_set_clear(pd_port, + DPM_REACTION_CAP_READY_ONCE, + DPM_REACTION_DFP_FLOW_DELAY); + } + break; +#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY */ + +#ifdef CONFIG_USB_PD_UFP_FLOW_DELAY + case PD_TIMER_UFP_FLOW_DELAY: + if (pd_port->pe_state_curr == ready_state && + pd_port->data_role == PD_ROLE_UFP) { + dpm_reaction_set_clear(pd_port, + DPM_REACTION_CAP_READY_ONCE, + DPM_REACTION_UFP_FLOW_DELAY); + } + break; +#endif /* CONFIG_USB_PD_UFP_FLOW_DELAY */ + +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY + case PD_TIMER_VCONN_STABLE: + if (pd_port->vconn_role == PD_ROLE_VCONN_DYNAMIC_ON) { + pd_set_vconn(pd_port, PD_ROLE_VCONN_ON); + dpm_reaction_set_clear(pd_port, + DPM_REACTION_CAP_READY_ONCE, + DPM_REACTION_VCONN_STABLE_DELAY); + } + break; +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + +#if defined(CONFIG_USB_PD_REV30) && defined(CONFIG_USB_PD_REV30_COLLISION_AVOID) + case PD_TIMER_DEFERRED_EVT: + pd_notify_tcp_event_buf_reset( + pd_port, TCP_DPM_RET_DROP_PE_BUSY); + break; +#endif + + default: + break; + } + + return false; +} + +bool pd_process_event_com( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->event_type) { + case PD_EVT_CTRL_MSG: + return pd_process_ctrl_msg(pd_port, pd_event); + + case PD_EVT_DATA_MSG: + return pd_process_data_msg(pd_port, pd_event); + +#ifdef CONFIG_USB_PD_REV30 + case PD_EVT_EXT_MSG: + return pd_process_ext_msg(pd_port, pd_event); +#endif /* CONFIG_USB_PD_REV30 */ + + case PD_EVT_DPM_MSG: + return pd_process_dpm_msg(pd_port, pd_event); + + case PD_EVT_HW_MSG: + return pd_process_hw_msg(pd_port, pd_event); + + case PD_EVT_TIMER_MSG: + return pd_process_timer_msg(pd_port, pd_event); + + default: + return false; + } +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_dbg.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_dbg.c new file mode 100644 index 000000000000..928cb70d1ba3 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_dbg.c @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event For DBGACC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" + +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + +bool pd_process_event_dbg(struct pd_port *pd_port, struct pd_event *pd_event) +{ + /* Don't need to handle any PD message, Keep VBUS 5V, and using VDM */ + return false; +} + +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_drs.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_drs.c new file mode 100644 index 000000000000..b2a9bcaddcd9 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_drs.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event For DRS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" + +/* PD Control MSG reactions */ + +DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_GOOD_CRC) = { + { PE_DRS_DFP_UFP_ACCEPT_DR_SWAP, PE_DRS_DFP_UFP_CHANGE_TO_UFP }, + { PE_DRS_UFP_DFP_ACCEPT_DR_SWAP, PE_DRS_UFP_DFP_CHANGE_TO_DFP }, +}; +DECL_PE_STATE_REACTION(PD_CTRL_MSG_GOOD_CRC); + +DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_ACCEPT) = { + { PE_DRS_DFP_UFP_SEND_DR_SWAP, PE_DRS_DFP_UFP_CHANGE_TO_UFP }, + { PE_DRS_UFP_DFP_SEND_DR_SWAP, PE_DRS_UFP_DFP_CHANGE_TO_DFP }, +}; +DECL_PE_STATE_REACTION(PD_CTRL_MSG_ACCEPT); + +/* DPM Event reactions */ + +DECL_PE_STATE_TRANSITION(PD_DPM_MSG_ACK) = { + { PE_DRS_DFP_UFP_EVALUATE_DR_SWAP, PE_DRS_DFP_UFP_ACCEPT_DR_SWAP }, + { PE_DRS_UFP_DFP_EVALUATE_DR_SWAP, PE_DRS_UFP_DFP_ACCEPT_DR_SWAP }, +}; +DECL_PE_STATE_REACTION(PD_DPM_MSG_ACK); + +DECL_PE_STATE_TRANSITION(PD_DPM_MSG_NAK) = { + { PE_DRS_DFP_UFP_EVALUATE_DR_SWAP, PE_DRS_DFP_UFP_REJECT_DR_SWAP }, + { PE_DRS_UFP_DFP_EVALUATE_DR_SWAP, PE_DRS_UFP_DFP_REJECT_DR_SWAP }, +}; +DECL_PE_STATE_REACTION(PD_DPM_MSG_NAK); + + +/* + * [BLOCK] Porcess PD Ctrl MSG + */ + +static inline bool pd_process_ctrl_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_CTRL_GOOD_CRC: + return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_GOOD_CRC); + + case PD_CTRL_ACCEPT: + return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_ACCEPT); + + default: + return false; + } +} + +/* + * [BLOCK] Porcess DPM MSG + */ + +static inline bool pd_process_dpm_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_DPM_ACK: + return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_ACK); + + case PD_DPM_NAK: + return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_NAK); + } + + return false; +} + +/* + * [BLOCK] Process Policy Engine's DRS Message + */ + +bool pd_process_event_drs(struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->event_type) { + case PD_EVT_CTRL_MSG: + return pd_process_ctrl_msg(pd_port, pd_event); + + case PD_EVT_DPM_MSG: + return pd_process_dpm_msg(pd_port, pd_event); + + default: + return false; + } +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_prs.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_prs.c new file mode 100644 index 000000000000..c0d277df020d --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_prs.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event For PRS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" +#include "inc/pd_dpm_core.h" + +#ifdef CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY +#define PE_PRS_SNK_HARD_RESET PE_ERROR_RECOVERY +#define PE_PRS_SRC_HARD_RESET PE_ERROR_RECOVERY +#else +#define PE_PRS_SNK_HARD_RESET PE_SNK_HARD_RESET +#define PE_PRS_SRC_HARD_RESET PE_SRC_HARD_RESET +#endif /* CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY */ + +/* PD Control MSG reactions */ + +DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_GOOD_CRC) = { + { PE_PRS_SRC_SNK_ACCEPT_PR_SWAP, PE_PRS_SRC_SNK_TRANSITION_TO_OFF }, + { PE_PRS_SNK_SRC_ACCEPT_PR_SWAP, PE_PRS_SNK_SRC_TRANSITION_TO_OFF }, + + /* VBUS-ON & PS_RDY SENT */ + { PE_PRS_SNK_SRC_SOURCE_ON, PE_SRC_STARTUP }, +}; +DECL_PE_STATE_REACTION(PD_CTRL_MSG_GOOD_CRC); + +DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_ACCEPT) = { + { PE_PRS_SRC_SNK_SEND_SWAP, PE_PRS_SRC_SNK_TRANSITION_TO_OFF }, + { PE_PRS_SNK_SRC_SEND_SWAP, PE_PRS_SNK_SRC_TRANSITION_TO_OFF }, +}; +DECL_PE_STATE_REACTION(PD_CTRL_MSG_ACCEPT); + +DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_PS_RDY) = { + { PE_PRS_SRC_SNK_WAIT_SOURCE_ON, PE_SNK_STARTUP }, + { PE_PRS_SNK_SRC_TRANSITION_TO_OFF, PE_PRS_SNK_SRC_ASSERT_RP }, +}; +DECL_PE_STATE_REACTION(PD_CTRL_MSG_PS_RDY); + +/* DPM Event reactions */ + +DECL_PE_STATE_TRANSITION(PD_DPM_MSG_ACK) = { + { PE_PRS_SRC_SNK_EVALUATE_PR_SWAP, PE_PRS_SRC_SNK_ACCEPT_PR_SWAP }, + { PE_PRS_SNK_SRC_EVALUATE_PR_SWAP, PE_PRS_SNK_SRC_ACCEPT_PR_SWAP }, + + { PE_PRS_SRC_SNK_ASSERT_RD, PE_PRS_SRC_SNK_WAIT_SOURCE_ON }, + { PE_PRS_SNK_SRC_ASSERT_RP, PE_PRS_SNK_SRC_SOURCE_ON }, +}; +DECL_PE_STATE_REACTION(PD_DPM_MSG_ACK); + +DECL_PE_STATE_TRANSITION(PD_DPM_MSG_NAK) = { + { PE_PRS_SRC_SNK_EVALUATE_PR_SWAP, PE_PRS_SRC_SNK_REJECT_PR_SWAP }, + { PE_PRS_SNK_SRC_EVALUATE_PR_SWAP, PE_PRS_SNK_SRC_REJECT_SWAP }, +}; +DECL_PE_STATE_REACTION(PD_DPM_MSG_NAK); + +/* HW Event reactions */ + +DECL_PE_STATE_TRANSITION(PD_HW_VBUS_PRESENT) = { +#ifdef CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP + { PE_PRS_SRC_SNK_WAIT_SOURCE_ON, PE_SNK_STARTUP }, +#endif /* CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP */ +}; +DECL_PE_STATE_REACTION(PD_HW_VBUS_PRESENT); + +#ifdef CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY +DECL_PE_STATE_TRANSITION(PD_HW_TX_FAILED) = { + { PE_PRS_SRC_SNK_WAIT_SOURCE_ON, PE_ERROR_RECOVERY }, + { PE_PRS_SNK_SRC_SOURCE_ON, PE_ERROR_RECOVERY }, +}; +DECL_PE_STATE_REACTION(PD_HW_TX_FAILED); +#endif /* CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY */ + +DECL_PE_STATE_TRANSITION(PD_HW_VBUS_SAFE0V) = { + { PE_PRS_SRC_SNK_TRANSITION_TO_OFF, PE_PRS_SRC_SNK_ASSERT_RD }, +#ifdef CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP + { PE_PRS_SNK_SRC_TRANSITION_TO_OFF, PE_PRS_SNK_SRC_ASSERT_RP }, +#endif /* CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP */ +}; +DECL_PE_STATE_REACTION(PD_HW_VBUS_SAFE0V); + +/* + * [BLOCK] Porcess PD Ctrl MSG + */ + +static inline bool pd_process_ctrl_msg_good_crc( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_port->pe_state_curr) { + case PE_PRS_SRC_SNK_WAIT_SOURCE_ON: + pd_enable_pe_state_timer(pd_port, PD_TIMER_PS_SOURCE_ON); + pd_unlock_msg_output(pd_port); /* for tSRCTransition */ + return false; + + default: + return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_GOOD_CRC); + } +} + +static inline bool pd_process_ctrl_msg_ps_rdy( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_port->pe_state_curr) { +#ifdef CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP + case PE_PRS_SRC_SNK_WAIT_SOURCE_ON: + pd_enable_vbus_valid_detection(pd_port, true); + return false; + + case PE_PRS_SNK_SRC_TRANSITION_TO_OFF: + pd_enable_vbus_safe0v_detection(pd_port); + return false; + +#endif /* CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP */ + default: + return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_PS_RDY); + } +} + +static inline bool pd_process_ctrl_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_CTRL_GOOD_CRC: + return pd_process_ctrl_msg_good_crc(pd_port, pd_event); + + case PD_CTRL_ACCEPT: + return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_ACCEPT); + + case PD_CTRL_PS_RDY: + return pd_process_ctrl_msg_ps_rdy(pd_port, pd_event); + + default: + return false; + } +} + +/* + * [BLOCK] Porcess DPM MSG + */ + +static inline bool pd_process_dpm_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_DPM_ACK: + return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_ACK); + + case PD_DPM_NAK: + return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_NAK); + } + + return false; +} + +/* + * [BLOCK] Porcess HW MSG + */ + +static inline bool pd_process_hw_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_HW_VBUS_PRESENT: + if (pd_port->pe_state_curr == PE_PRS_SNK_SRC_SOURCE_ON) + pd_send_sop_ctrl_msg(pd_port, PD_CTRL_PS_RDY); + + return PE_MAKE_STATE_TRANSIT(PD_HW_VBUS_PRESENT); + +#ifdef CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY + case PD_HW_TX_FAILED: + return PE_MAKE_STATE_TRANSIT(PD_HW_TX_FAILED); +#endif /* CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY */ + + case PD_HW_VBUS_SAFE0V: + return PE_MAKE_STATE_TRANSIT(PD_HW_VBUS_SAFE0V); + + default: + return false; + } +} + +/* + * [BLOCK] Porcess Timer MSG + */ + +static inline bool pd_process_timer_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_TIMER_PS_SOURCE_ON: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_PRS_SRC_SNK_WAIT_SOURCE_ON, PE_PRS_SNK_HARD_RESET); + + case PD_TIMER_PS_SOURCE_OFF: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_PRS_SNK_SRC_TRANSITION_TO_OFF, + PE_PRS_SNK_HARD_RESET); + + case PD_TIMER_SOURCE_TRANSITION: + pd_dpm_prs_enable_power_source(pd_port, false); + return false; + + default: + return false; + } +} + +/* + * [BLOCK] Process Policy Engine's PRS Message + */ + +bool pd_process_event_prs(struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->event_type) { + case PD_EVT_CTRL_MSG: + return pd_process_ctrl_msg(pd_port, pd_event); + + case PD_EVT_DPM_MSG: + return pd_process_dpm_msg(pd_port, pd_event); + + case PD_EVT_HW_MSG: + return pd_process_hw_msg(pd_port, pd_event); + + case PD_EVT_TIMER_MSG: + return pd_process_timer_msg(pd_port, pd_event); + + default: + return false; + } +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_snk.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_snk.c new file mode 100644 index 000000000000..62a2225b2541 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_snk.c @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event For SNK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" +#include "inc/tcpci_typec.h" + +/* PD Control MSG reactions */ + +DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_ACCEPT) = { + { PE_SNK_SELECT_CAPABILITY, PE_SNK_TRANSITION_SINK }, + { PE_SNK_SEND_SOFT_RESET, PE_SNK_WAIT_FOR_CAPABILITIES }, +}; +DECL_PE_STATE_REACTION(PD_CTRL_MSG_ACCEPT); + +/* PD Data MSG reactions */ + +DECL_PE_STATE_TRANSITION(PD_DATA_MSG_SOURCE_CAP) = { + { PE_SNK_WAIT_FOR_CAPABILITIES, PE_SNK_EVALUATE_CAPABILITY }, + { PE_SNK_READY, PE_SNK_EVALUATE_CAPABILITY }, + + /* PR-Swap issue (Check it later) */ + { PE_SNK_STARTUP, PE_SNK_EVALUATE_CAPABILITY }, + { PE_SNK_DISCOVERY, PE_SNK_EVALUATE_CAPABILITY }, + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + { PE_SNK_GET_SOURCE_CAP, PE_SNK_EVALUATE_CAPABILITY }, +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ +}; +DECL_PE_STATE_REACTION(PD_DATA_MSG_SOURCE_CAP); + +/* + * [BLOCK] Porcess Ctrl MSG + */ + +static bool pd_process_ctrl_msg_get_source_cap( + struct pd_port *pd_port, uint8_t next) +{ + if (pd_port->pe_state_curr != PE_SNK_READY) + return false; + +#ifdef CONFIG_USB_PD_PR_SWAP + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) { + PE_TRANSIT_STATE(pd_port, next); + return true; + } +#endif /* CONFIG_USB_PD_PR_SWAP */ + + if (!pd_check_rev30(pd_port)) { + PE_TRANSIT_STATE(pd_port, PE_REJECT); + return true; + } + + return false; +} + +static inline bool pd_process_ctrl_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#ifdef CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + switch (pd_port->pe_state_curr) { + case PE_SNK_GET_SOURCE_CAP: + +#ifdef CONFIG_USB_PD_PR_SWAP + case PE_DR_SNK_GET_SINK_CAP: +#endif /* CONFIG_USB_PD_PR_SWAP */ + if (pd_event->msg >= PD_CTRL_GET_SOURCE_CAP && + pd_event->msg <= PD_CTRL_VCONN_SWAP) { + PE_DBG("Port Partner Request First\n"); + pd_port->pe_state_curr = PE_SNK_READY; + pd_disable_timer( + pd_port, PD_TIMER_SENDER_RESPONSE); + } + break; + } +#endif /* CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST */ + + switch (pd_event->msg) { + case PD_CTRL_GOOD_CRC: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_SOFT_RESET, PE_SNK_WAIT_FOR_CAPABILITIES); + + case PD_CTRL_GOTO_MIN: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_READY, PE_SNK_TRANSITION_SINK)) + return true; + break; + + case PD_CTRL_ACCEPT: + if (PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_ACCEPT)) + return true; + break; + + case PD_CTRL_PS_RDY: + switch (pd_port->pe_state_curr) { + case PE_SNK_TRANSITION_SINK: + pd_dpm_snk_transition_power(pd_port); + PE_TRANSIT_STATE(pd_port, PE_SNK_READY); + return true; + +#ifdef CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP + case PE_PRS_SRC_SNK_WAIT_SOURCE_ON: + case PE_PRS_SNK_SRC_TRANSITION_TO_OFF: + return false; +#endif /* CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP */ + default: + break; + } + break; + + case PD_CTRL_GET_SOURCE_CAP: + if (pd_process_ctrl_msg_get_source_cap( + pd_port, PE_DR_SNK_GIVE_SOURCE_CAP)) + return true; + break; + + case PD_CTRL_GET_SINK_CAP: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_READY, PE_SNK_GIVE_SINK_CAP)) + return true; + break; + + case PD_CTRL_REJECT: + case PD_CTRL_WAIT: + if (pd_port->pe_state_curr == PE_SNK_SELECT_CAPABILITY) { + if (pd_port->pe_data.explicit_contract) + PE_TRANSIT_STATE(pd_port, PE_SNK_READY); + else { + PE_TRANSIT_STATE(pd_port, + PE_SNK_WAIT_FOR_CAPABILITIES); + } + return true; + } + break; + +#ifdef CONFIG_USB_PD_REV30 + case PD_CTRL_NOT_SUPPORTED: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_READY, PE_SNK_NOT_SUPPORTED_RECEIVED)) + return true; + break; + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + case PD_CTRL_GET_SOURCE_CAP_EXT: + if (pd_process_ctrl_msg_get_source_cap( + pd_port, PE_DR_SNK_GIVE_SOURCE_CAP_EXT)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + case PD_CTRL_GET_STATUS: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_READY, PE_SNK_GIVE_SINK_STATUS)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ +#endif /* CONFIG_USB_PD_REV30 */ + + default: + pd_port->curr_unsupported_msg = true; + break; + } + + return pd_process_protocol_error(pd_port, pd_event); +} + +/* + * [BLOCK] Porcess Data MSG + */ + +static inline bool pd_process_data_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_DATA_SOURCE_CAP: +#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP + pd_port->msg_id_pr_swap_last = 0xff; +#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */ + if (PE_MAKE_STATE_TRANSIT(PD_DATA_MSG_SOURCE_CAP)) + return true; + break; + +#ifdef CONFIG_USB_PD_PR_SWAP + case PD_DATA_SINK_CAP: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_DR_SNK_GET_SINK_CAP, PE_SNK_READY)) + return true; + break; +#endif /* CONFIG_USB_PD_PR_SWAP */ + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + case PD_DATA_ALERT: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_READY, PE_SNK_SOURCE_ALERT_RECEIVED)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ + default: + pd_port->curr_unsupported_msg = true; + break; + } + + return pd_process_protocol_error(pd_port, pd_event); +} + +/* + * [BLOCK] Porcess Extend MSG + */ +#ifdef CONFIG_USB_PD_REV30 +static inline bool pd_process_ext_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + case PD_EXT_SOURCE_CAP_EXT: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_GET_SOURCE_CAP_EXT, PE_SNK_READY)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + case PD_EXT_STATUS: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_GET_SOURCE_STATUS, PE_SNK_READY)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + case PD_EXT_PPS_STATUS: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_GET_PPS_STATUS, PE_SNK_READY)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + default: + pd_port->curr_unsupported_msg = true; + break; + } + + return pd_process_protocol_error(pd_port, pd_event); +} +#endif /* CONFIG_USB_PD_REV30 */ + +/* + * [BLOCK] Porcess DPM MSG + */ + +static inline bool pd_process_dpm_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_DPM_ACK: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_EVALUATE_CAPABILITY, PE_SNK_SELECT_CAPABILITY); + + default: + return false; + } +} + +/* + * [BLOCK] Porcess HW MSG + */ + +static inline bool pd_process_hw_msg_sink_tx_change( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + struct pe_data *pe_data = &pd_port->pe_data; + uint8_t pd_traffic; + +#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP + if (pe_data->pd_traffic_control == PD_SINK_TX_START) + return false; +#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */ + + if (!pd_check_rev30(pd_port)) + return false; + + pd_traffic = pd_event->msg_sec ? + PD_SINK_TX_OK : PD_SINK_TX_NG; + + if (pe_data->pd_traffic_control == pd_traffic) + return false; + + pe_data->pd_traffic_control = pd_traffic; + dpm_reaction_set_ready_once(pd_port); +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + + return false; +} + +static inline bool pd_process_vbus_absent(struct pd_port *pd_port) +{ + if (pd_port->pe_state_curr != PE_SNK_DISCOVERY) + return false; +#ifdef CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW + /* iSafe0mA: Maximum current a Sink + * is allowed to draw when VBUS is driven to vSafe0V + */ + pd_dpm_sink_vbus(pd_port, false); +#endif /* CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW */ + pd_disable_pe_state_timer(pd_port); + pd_enable_vbus_valid_detection(pd_port, true); + return false; +} + +static inline bool pd_process_hw_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_HW_VBUS_PRESENT: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_DISCOVERY, PE_SNK_WAIT_FOR_CAPABILITIES); + + case PD_HW_VBUS_ABSENT: + return pd_process_vbus_absent(pd_port); + + case PD_HW_TX_FAILED: + return pd_process_tx_failed(pd_port); + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + case PD_HW_SINK_TX_CHANGE: + return pd_process_hw_msg_sink_tx_change(pd_port, pd_event); +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + }; + + return false; +} + +/* + * [BLOCK] Porcess PE MSG + */ + +static inline bool pd_process_pe_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_PE_RESET_PRL_COMPLETED: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_STARTUP, PE_SNK_DISCOVERY); + + case PD_PE_HARD_RESET_COMPLETED: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_HARD_RESET, PE_SNK_TRANSITION_TO_DEFAULT); + + case PD_PE_POWER_ROLE_AT_DEFAULT: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_TRANSITION_TO_DEFAULT, PE_SNK_STARTUP); + + default: + return false; + } + +} + +/* + * [BLOCK] Porcess Timer MSG + */ + +static inline void pd_report_typec_only_charger(struct pd_port *pd_port) +{ + uint8_t state; + struct tcpc_device *tcpc = pd_port->tcpc; + + if (tcpc->typec_remote_rp_level == TYPEC_CC_VOLT_SNK_DFT) + state = PD_CONNECT_TYPEC_ONLY_SNK_DFT; + else + state = PD_CONNECT_TYPEC_ONLY_SNK; + + PE_INFO("TYPE-C Only Charger!\n"); + pd_dpm_sink_vbus(pd_port, true); + pd_set_rx_enable(pd_port, PD_RX_CAP_PE_IDLE); + pd_notify_pe_hard_reset_completed(pd_port); + pd_update_connect_state(pd_port, state); +} + +static inline bool pd_process_timer_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; +#endif /* CONFIG_USB_PD_DBG_IGRONE_TIMEOUT */ + struct pe_data __maybe_unused *pe_data = &pd_port->pe_data; + + switch (pd_event->msg) { + case PD_TIMER_SINK_REQUEST: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_READY, PE_SNK_SELECT_CAPABILITY); + +#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT + case PD_TIMER_SINK_WAIT_CAP: + case PD_TIMER_PS_TRANSITION: + if ((pd_port->pe_state_curr != PE_SNK_DISCOVERY) && + (pe_data->hard_reset_counter <= PD_HARD_RESET_COUNT)) { + PE_TRANSIT_STATE(pd_port, PE_SNK_HARD_RESET); + return true; + } + + PE_INFO("SRC NoResp\n"); + if (pd_port->request_v == TCPC_VBUS_SINK_5V) { + pd_report_typec_only_charger(pd_port); + } else { + PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY); + return true; + } + break; +#endif /* CONFIG_USB_PD_DBG_IGRONE_TIMEOUT */ + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID + case PD_TIMER_DISCOVER_ID: + vdm_put_dpm_discover_cable_event(pd_port); + break; +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ + /* fall through */ +#ifdef CONFIG_USB_PD_REV30 + case PD_TIMER_CK_NOT_SUPPORTED: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SNK_CHUNK_RECEIVED, PE_SNK_SEND_NOT_SUPPORTED)) + return true; + /* fall through */ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID +#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP + case PD_TIMER_SNK_FLOW_DELAY: + if (pe_data->pd_traffic_control == PD_SINK_TX_START) { + if (typec_get_cc_res() == TYPEC_CC_VOLT_SNK_3_0) + pe_data->pd_traffic_control = PD_SINK_TX_OK; + else + pe_data->pd_traffic_control = PD_SINK_TX_NG; + if (pd_check_rev30(pd_port)) + dpm_reaction_set_ready_once(pd_port); + } + break; +#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */ +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + } + + return false; +} + +/* + * [BLOCK] Process Policy Engine's SNK Message + */ + +bool pd_process_event_snk(struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->event_type) { + case PD_EVT_CTRL_MSG: + return pd_process_ctrl_msg(pd_port, pd_event); + + case PD_EVT_DATA_MSG: + return pd_process_data_msg(pd_port, pd_event); + +#ifdef CONFIG_USB_PD_REV30 + case PD_EVT_EXT_MSG: + return pd_process_ext_msg(pd_port, pd_event); +#endif /* CONFIG_USB_PD_REV30 */ + + case PD_EVT_DPM_MSG: + return pd_process_dpm_msg(pd_port, pd_event); + + case PD_EVT_HW_MSG: + return pd_process_hw_msg(pd_port, pd_event); + + case PD_EVT_PE_MSG: + return pd_process_pe_msg(pd_port, pd_event); + + case PD_EVT_TIMER_MSG: + return pd_process_timer_msg(pd_port, pd_event); + + default: + return false; + } +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_src.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_src.c new file mode 100644 index 000000000000..a79cff1bf3b7 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_src.c @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event For SRC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" +#include "inc/pd_dpm_core.h" + + +/* PD Data MSG reactions */ + +DECL_PE_STATE_TRANSITION(PD_DATA_MSG_REQUEST) = { + { PE_SRC_SEND_CAPABILITIES, PE_SRC_NEGOTIATE_CAPABILITIES }, + { PE_SRC_READY, PE_SRC_NEGOTIATE_CAPABILITIES }, +}; +DECL_PE_STATE_REACTION(PD_DATA_MSG_REQUEST); + +/* DPM Event reactions */ + +DECL_PE_STATE_TRANSITION(PD_DPM_MSG_ACK) = { + { PE_SRC_NEGOTIATE_CAPABILITIES, PE_SRC_TRANSITION_SUPPLY }, +}; +DECL_PE_STATE_REACTION(PD_DPM_MSG_ACK); + +DECL_PE_STATE_TRANSITION(PD_DPM_MSG_CAP_CHANGED) = { + { PE_SRC_READY, PE_SRC_SEND_CAPABILITIES }, + { PE_SRC_WAIT_NEW_CAPABILITIES, PE_SRC_SEND_CAPABILITIES }, +}; +DECL_PE_STATE_REACTION(PD_DPM_MSG_CAP_CHANGED); + +/* Timer Event reactions */ + +DECL_PE_STATE_TRANSITION(PD_TIMER_PS_HARD_RESET) = { + { PE_SRC_HARD_RESET, PE_SRC_TRANSITION_TO_DEFAULT }, + { PE_SRC_HARD_RESET_RECEIVED, PE_SRC_TRANSITION_TO_DEFAULT }, +}; +DECL_PE_STATE_REACTION(PD_TIMER_PS_HARD_RESET); + +/* + * [BLOCK] Porcess Ctrl MSG + */ + +static inline bool pd_process_ctrl_msg_good_crc( + struct pd_port *pd_port, struct pd_event *pd_event) + +{ + switch (pd_port->pe_state_curr) { + case PE_SRC_SEND_CAPABILITIES: + pd_port->pe_data.cap_counter = 0; + pd_handle_hard_reset_recovery(pd_port); + return false; + + case PE_SRC_TRANSITION_SUPPLY: + pd_enable_pe_state_timer(pd_port, PD_TIMER_SOURCE_TRANSITION); + return false; + + case PE_SRC_CAPABILITY_RESPONSE: + if (!pd_port->pe_data.explicit_contract) + PE_TRANSIT_STATE(pd_port, PE_SRC_WAIT_NEW_CAPABILITIES); + else if (pd_port->pe_data.invalid_contract) + PE_TRANSIT_STATE(pd_port, PE_SRC_HARD_RESET); + else + PE_TRANSIT_STATE(pd_port, PE_SRC_READY); + return true; + + case PE_SRC_SOFT_RESET: + PE_TRANSIT_STATE(pd_port, PE_SRC_SEND_CAPABILITIES); + return true; + + default: + return false; + } +} + +static inline bool pd_process_ctrl_msg_get_sink_cap( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + if (pd_port->pe_state_curr != PE_SRC_READY) + return false; + +#ifdef CONFIG_USB_PD_PR_SWAP + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) { + PE_TRANSIT_STATE(pd_port, PE_DR_SRC_GIVE_SINK_CAP); + return true; + } +#endif /* CONFIG_USB_PD_PR_SWAP */ + + if (!pd_check_rev30(pd_port)) { + PE_TRANSIT_STATE(pd_port, PE_REJECT); + return true; + } + + return false; +} + +static inline bool pd_process_ctrl_msg( + struct pd_port *pd_port, struct pd_event *pd_event) + +{ +#ifdef CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + switch (pd_port->pe_state_curr) { + case PE_SRC_GET_SINK_CAP: + +#ifdef CONFIG_USB_PD_PR_SWAP + case PE_DR_SRC_GET_SOURCE_CAP: +#endif /* CONFIG_USB_PD_PR_SWAP */ + if (pd_event->msg >= PD_CTRL_GET_SOURCE_CAP && + pd_event->msg <= PD_CTRL_VCONN_SWAP) { + PE_DBG("Port Partner Request First\n"); + pd_port->pe_state_curr = PE_SRC_READY; + pd_disable_timer( + pd_port, PD_TIMER_SENDER_RESPONSE); + } + break; + } +#endif /* CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST */ + + switch (pd_event->msg) { + case PD_CTRL_GOOD_CRC: + return pd_process_ctrl_msg_good_crc(pd_port, pd_event); + + case PD_CTRL_ACCEPT: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_SEND_SOFT_RESET, PE_SRC_SEND_CAPABILITIES)) + return true; + break; + + case PD_CTRL_GET_SOURCE_CAP: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_READY, PE_SRC_SEND_CAPABILITIES)) + return true; + break; + + case PD_CTRL_GET_SINK_CAP: + if (pd_process_ctrl_msg_get_sink_cap(pd_port, pd_event)) + return true; + break; + +#ifdef CONFIG_USB_PD_REV30 + case PD_CTRL_NOT_SUPPORTED: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_READY, PE_SRC_NOT_SUPPORTED_RECEIVED)) + return true; + break; + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL + case PD_CTRL_GET_SOURCE_CAP_EXT: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_READY, PE_SRC_GIVE_SOURCE_CAP_EXT)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + case PD_CTRL_GET_STATUS: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_READY, PE_SRC_GIVE_SOURCE_STATUS)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + case PD_CTRL_GET_PPS_STATUS: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_READY, PE_SRC_GIVE_PPS_STATUS)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ + + default: + pd_port->curr_unsupported_msg = true; + break; + } + + return pd_process_protocol_error(pd_port, pd_event); +} + +/* + * [BLOCK] Porcess Data MSG + */ + +static inline bool pd_process_data_msg( + struct pd_port *pd_port, struct pd_event *pd_event) + +{ + switch (pd_event->msg) { +#ifdef CONFIG_USB_PD_PR_SWAP + case PD_DATA_SOURCE_CAP: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_DR_SRC_GET_SOURCE_CAP, PE_SRC_READY)) + return true; + break; +#endif /* CONFIG_USB_PD_PR_SWAP */ + + case PD_DATA_SINK_CAP: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_GET_SINK_CAP, PE_SRC_READY)) + return true; + break; + + case PD_DATA_REQUEST: + if (PE_MAKE_STATE_TRANSIT(PD_DATA_MSG_REQUEST)) + return true; + break; + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE + case PD_DATA_ALERT: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_READY, PE_SRC_SINK_ALERT_RECEIVED)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ + + default: + pd_port->curr_unsupported_msg = true; + break; + } + + return pd_process_protocol_error(pd_port, pd_event); +} + +/* + * [BLOCK] Porcess Extend MSG + */ +#ifdef CONFIG_USB_PD_REV30 + +static inline bool pd_process_ext_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + case PD_EXT_SOURCE_CAP_EXT: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_DR_SRC_GET_SOURCE_CAP_EXT, PE_SRC_READY)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + case PD_EXT_STATUS: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_GET_SINK_STATUS, PE_SRC_READY)) + return true; + break; +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + + default: + pd_port->curr_unsupported_msg = true; + break; + } + + return pd_process_protocol_error(pd_port, pd_event); +} + +#endif /* CONFIG_USB_PD_REV30 */ + +/* + * [BLOCK] Porcess DPM MSG + */ + +static inline bool pd_process_dpm_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_DPM_ACK: + return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_ACK); + + case PD_DPM_NAK: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_NEGOTIATE_CAPABILITIES, + PE_SRC_CAPABILITY_RESPONSE); + + case PD_DPM_CAP_CHANGED: + return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_CAP_CHANGED); + + default: + return false; + } +} + +/* + * [BLOCK] Porcess HW MSG + */ + +static inline bool pd_process_hw_msg_vbus_present( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_port->pe_state_curr) { + case PE_SRC_STARTUP: + pd_enable_timer(pd_port, PD_TIMER_SOURCE_START); + break; + + case PE_SRC_TRANSITION_TO_DEFAULT: + pd_put_pe_event(pd_port, PD_PE_POWER_ROLE_AT_DEFAULT); + break; + } + + return false; +} + +static inline bool pd_process_hw_msg_tx_failed( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + struct pe_data *pe_data = &pd_port->pe_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->pe_state_curr == PE_SRC_SEND_CAPABILITIES) { + if (pe_data->pd_connected) { + if (!pe_data->explicit_contract) { + PE_DBG("PR_SWAP NoResp\n"); + return false; + } + } else { + PE_TRANSIT_STATE(pd_port, PE_SRC_DISCOVERY); + return true; + } + } + +#ifdef CONFIG_PD_SRC_RESET_CABLE + if (pd_port->pe_state_curr == PE_SRC_CBL_SEND_SOFT_RESET) { + PE_TRANSIT_STATE(pd_port, PE_SRC_SEND_CAPABILITIES); + return true; + } +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + + return pd_process_tx_failed(pd_port); +} + +static inline bool pd_process_hw_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_HW_VBUS_PRESENT: + return pd_process_hw_msg_vbus_present(pd_port, pd_event); + + case PD_HW_VBUS_SAFE0V: + pd_enable_timer(pd_port, PD_TIMER_SRC_RECOVER); + return false; + + case PD_HW_VBUS_STABLE: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_TRANSITION_SUPPLY, PE_SRC_TRANSITION_SUPPLY2); + + case PD_HW_TX_FAILED: + return pd_process_hw_msg_tx_failed(pd_port, pd_event); + + default: + return false; + }; +} + +/* + * [BLOCK] Porcess PE MSG + */ + +static inline bool pd_process_pe_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_PE_RESET_PRL_COMPLETED: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_STARTUP, PE_SRC_SEND_CAPABILITIES); + + case PD_PE_POWER_ROLE_AT_DEFAULT: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_TRANSITION_TO_DEFAULT, PE_SRC_STARTUP); + + default: + return false; + } +} + +/* + * [BLOCK] Porcess Timer MSG + */ +static inline bool pd_process_timer_msg_source_start( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID + if (pd_is_discover_cable(pd_port) && + pd_port->pe_data.msg_id_tx[TCPC_TX_SOP_PRIME] == 0) { + +#ifdef CONFIG_PD_SRC_RESET_CABLE + if (pd_is_reset_cable(pd_port)) { + PE_TRANSIT_STATE(pd_port, PE_SRC_CBL_SEND_SOFT_RESET); + return true; + } +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + + if (vdm_put_dpm_discover_cable_event(pd_port)) + return false; + } +#endif /* CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID */ + + switch (pd_port->pe_state_curr) { + case PE_SRC_STARTUP: +#ifdef CONFIG_PD_SRC_RESET_CABLE + case PE_SRC_CBL_SEND_SOFT_RESET: +#endif /* CONFIG_PD_SRC_RESET_CABLE */ + PE_TRANSIT_STATE(pd_port, PE_SRC_SEND_CAPABILITIES); + return true; + } + + return false; +}; + +static inline bool pd_process_timer_msg_source_cap( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + if (pd_port->pe_state_curr != PE_SRC_DISCOVERY) + return false; + + if (pd_port->pe_data.cap_counter <= PD_CAPS_COUNT) + PE_TRANSIT_STATE(pd_port, PE_SRC_SEND_CAPABILITIES); + else /* in this state, PD always not connected */ + PE_TRANSIT_STATE(pd_port, PE_SRC_DISABLED); + + return true; +} + +static inline bool pd_process_timer_msg_no_response( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + if (pd_port->pe_data.hard_reset_counter <= PD_HARD_RESET_COUNT) + PE_TRANSIT_STATE(pd_port, PE_SRC_HARD_RESET); + else if (pd_port->pe_data.pd_prev_connected) + PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY); + else + PE_TRANSIT_STATE(pd_port, PE_SRC_DISABLED); + + return true; +} + +static inline bool pd_process_timer_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_TIMER_SOURCE_CAPABILITY: + return pd_process_timer_msg_source_cap(pd_port, pd_event); + +#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT +#ifdef CONFIG_PD_SRC_RESET_CABLE + case PD_TIMER_SENDER_RESPONSE: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_CBL_SEND_SOFT_RESET, PE_SRC_SEND_CAPABILITIES); +#endif /* CONFIG_PD_SRC_RESET_CABLE */ +#endif + + case PD_TIMER_PS_HARD_RESET: + return PE_MAKE_STATE_TRANSIT(PD_TIMER_PS_HARD_RESET); + + case PD_TIMER_SOURCE_START: + return pd_process_timer_msg_source_start(pd_port, pd_event); + +#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT + case PD_TIMER_NO_RESPONSE: + return pd_process_timer_msg_no_response(pd_port, pd_event); +#endif + + case PD_TIMER_SOURCE_TRANSITION: + if (pd_port->state_machine != PE_STATE_MACHINE_PR_SWAP) + pd_dpm_src_transition_power(pd_port); + break; + +#ifdef CONFIG_PD_DISCOVER_CABLE_ID + case PD_TIMER_DISCOVER_ID: + vdm_put_dpm_discover_cable_event(pd_port); + break; +#endif /* CONFIG_PD_DISCOVER_CABLE_ID */ + + case PD_TIMER_SRC_RECOVER: + pd_dpm_source_vbus(pd_port, true); + pd_enable_vbus_valid_detection(pd_port, true); + break; + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + case PD_TIMER_SINK_TX: + if (pd_port->pe_data.pd_traffic_control == PD_SINK_TX_NG) + pd_port->pe_data.pd_traffic_control = PD_SOURCE_TX_OK; + +#ifdef CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP + if (pd_port->pe_data.pd_traffic_control == PD_SOURCE_TX_START) + pd_port->pe_data.pd_traffic_control = PD_SINK_TX_OK; +#endif /* CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP */ + + break; +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + /* fall through */ +#ifdef CONFIG_USB_PD_REV30 + case PD_TIMER_CK_NOT_SUPPORTED: + return PE_MAKE_STATE_TRANSIT_SINGLE( + PE_SRC_CHUNK_RECEIVED, PE_SRC_SEND_NOT_SUPPORTED); +#endif /* CONFIG_USB_PD_REV30 */ + } + + return false; +} + +/* + * [BLOCK] Process Policy Engine's SRC Message + */ + +bool pd_process_event_src(struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->event_type) { + case PD_EVT_CTRL_MSG: + return pd_process_ctrl_msg(pd_port, pd_event); + + case PD_EVT_DATA_MSG: + return pd_process_data_msg(pd_port, pd_event); + +#ifdef CONFIG_USB_PD_REV30 + case PD_EVT_EXT_MSG: + return pd_process_ext_msg(pd_port, pd_event); +#endif /* CONFIG_USB_PD_REV30 */ + + case PD_EVT_DPM_MSG: + return pd_process_dpm_msg(pd_port, pd_event); + + case PD_EVT_HW_MSG: + return pd_process_hw_msg(pd_port, pd_event); + + case PD_EVT_PE_MSG: + return pd_process_pe_msg(pd_port, pd_event); + + case PD_EVT_TIMER_MSG: + return pd_process_timer_msg(pd_port, pd_event); + + default: + return false; + } +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_tcp.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_tcp.c new file mode 100644 index 000000000000..986e105789ff --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_tcp.c @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event for TCP + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" +#include "inc/pd_dpm_core.h" + +#ifdef CONFIG_USB_PD_PR_SWAP +static inline int pd_handle_tcp_event_pr_swap( + struct pd_port *pd_port, uint8_t new_role) +{ + if (pd_port->power_role == new_role) + return TCP_DPM_RET_DENIED_SAME_ROLE; + + if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER)) + return TCP_DPM_RET_DENIED_LOCAL_CAP; + + if (!pd_check_pe_state_ready(pd_port)) + return TCP_DPM_RET_DENIED_NOT_READY; + + pd_port->pe_data.during_swap = false; + pd_port->state_machine = PE_STATE_MACHINE_PR_SWAP; + + pe_transit_send_pr_swap_state(pd_port); + return TCP_DPM_RET_SENT; +} +#endif /* CONFIG_USB_PD_PR_SWAP */ + +#ifdef CONFIG_USB_PD_DR_SWAP +static inline int pd_handle_tcp_event_dr_swap( + struct pd_port *pd_port, uint8_t new_role) +{ + if (pd_port->data_role == new_role) + return TCP_DPM_RET_DENIED_SAME_ROLE; + + if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_DR_DATA)) + return TCP_DPM_RET_DENIED_LOCAL_CAP; + + if (!pd_check_pe_state_ready(pd_port)) + return TCP_DPM_RET_DENIED_NOT_READY; + + pd_port->pe_data.during_swap = false; + pd_port->state_machine = PE_STATE_MACHINE_DR_SWAP; + + PE_TRANSIT_DATA_STATE(pd_port, + PE_DRS_UFP_DFP_SEND_DR_SWAP, + PE_DRS_DFP_UFP_SEND_DR_SWAP); + + return TCP_DPM_RET_SENT; +} +#endif /* CONFIG_USB_PD_DR_SWAP */ + +#ifdef CONFIG_USB_PD_VCONN_SWAP +static inline int pd_handle_tcp_event_vconn_swap( + struct pd_port *pd_port, uint8_t new_role) +{ + uint8_t old_role = pd_port->vconn_role ? 1 : 0; + + if (old_role == new_role) + return TCP_DPM_RET_DENIED_SAME_ROLE; + + if ((!pd_port->vconn_role) && + (!(pd_port->dpm_caps & DPM_CAP_LOCAL_VCONN_SUPPLY))) + return TCP_DPM_RET_DENIED_LOCAL_CAP; + + if (!pd_check_pe_state_ready(pd_port)) + return TCP_DPM_RET_DENIED_NOT_READY; + + pd_port->state_machine = PE_STATE_MACHINE_VCONN_SWAP; + PE_TRANSIT_STATE(pd_port, PE_VCS_SEND_SWAP); + return TCP_DPM_RET_SENT; +} +#endif /* CONFIG_USB_PD_VCONN_SWAP */ + +#ifdef CONFIG_USB_PD_PE_SOURCE +static inline int pd_handle_tcp_event_gotomin(struct pd_port *pd_port) +{ + if (pd_port->pe_state_curr != PE_SRC_READY) + return TCP_DPM_RET_DENIED_NOT_READY; + + if (!(pd_port->pe_data.dpm_flags & DPM_FLAGS_PARTNER_GIVE_BACK)) + return TCP_DPM_RET_DENIED_PARTNER_CAP; + + PE_TRANSIT_STATE(pd_port, PE_SRC_TRANSITION_SUPPLY); + return TCP_DPM_RET_SENT; +} +#endif /* CONFIG_USB_PD_PE_SOURCE */ + +static inline int pd_handle_tcp_event_softreset(struct pd_port *pd_port) +{ + if (!pd_check_pe_state_ready(pd_port)) + return TCP_DPM_RET_DENIED_NOT_READY; + + pe_transit_soft_reset_state(pd_port); + return TCP_DPM_RET_SENT; +} + +#ifdef CONFIG_PD_DFP_RESET_CABLE +static inline int pd_handle_tcp_event_cable_softreset(struct pd_port *pd_port) +{ + bool role_check; + + if (!pd_check_pe_state_ready(pd_port)) + return TCP_DPM_RET_DENIED_NOT_READY; + + role_check = pd_port->data_role == PD_ROLE_DFP; + + if (pd_check_rev30(pd_port)) + role_check = pd_port->vconn_role; + + if (!role_check) + return TCP_DPM_RET_DENIED_WRONG_DATA_ROLE; + + PE_TRANSIT_STATE(pd_port, PE_DFP_CBL_SEND_SOFT_RESET); + return TCP_DPM_RET_SENT; +} +#endif /* CONFIG_PD_DFP_RESET_CABLE */ + +static inline int pd_handle_tcp_event_get_source_cap(struct pd_port *pd_port) +{ + switch (pd_port->pe_state_curr) { + case PE_SNK_READY: + PE_TRANSIT_STATE(pd_port, PE_SNK_GET_SOURCE_CAP); + return TCP_DPM_RET_SENT; + +#ifdef CONFIG_USB_PD_PR_SWAP + case PE_SRC_READY: + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) { + PE_TRANSIT_STATE(pd_port, PE_DR_SRC_GET_SOURCE_CAP); + return TCP_DPM_RET_SENT; + } +#endif /* CONFIG_USB_PD_PR_SWAP */ + + return TCP_DPM_RET_DENIED_LOCAL_CAP; + } + + return TCP_DPM_RET_DENIED_NOT_READY; +} + +static inline int pd_handle_tcp_event_get_sink_cap(struct pd_port *pd_port) +{ + switch (pd_port->pe_state_curr) { + case PE_SRC_READY: + PE_TRANSIT_STATE(pd_port, PE_SRC_GET_SINK_CAP); + return TCP_DPM_RET_SENT; + +#ifdef CONFIG_USB_PD_PR_SWAP + case PE_SNK_READY: + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) { + PE_TRANSIT_STATE(pd_port, PE_DR_SNK_GET_SINK_CAP); + return TCP_DPM_RET_SENT; + } +#endif /* CONFIG_USB_PD_PR_SWAP */ + + return TCP_DPM_RET_DENIED_LOCAL_CAP; + } + + return TCP_DPM_RET_DENIED_NOT_READY; +} + +#ifdef CONFIG_USB_PD_PE_SINK +static inline int pd_handle_tcp_event_request(struct pd_port *pd_port) +{ + int ret = 0; + struct tcp_dpm_event *tcp_event = &pd_port->tcp_event; + + if (pd_port->pe_state_curr != PE_SNK_READY) + return TCP_DPM_RET_DENIED_NOT_READY; + + switch (pd_get_curr_pd_event(pd_port)->msg) { + case TCP_DPM_EVT_REQUEST: + ret = pd_dpm_update_tcp_request( + pd_port, &tcp_event->tcp_dpm_data.pd_req); + break; + case TCP_DPM_EVT_REQUEST_EX: + ret = pd_dpm_update_tcp_request_ex( + pd_port, &tcp_event->tcp_dpm_data.pd_req_ex); + break; + case TCP_DPM_EVT_REQUEST_AGAIN: + ret = pd_dpm_update_tcp_request_again(pd_port); + break; + } + + if (ret != TCP_DPM_RET_SUCCESS) + return ret; + + PE_TRANSIT_STATE(pd_port, PE_SNK_SELECT_CAPABILITY); + return TCP_DPM_RET_SENT; +} +#endif /* CONFIG_USB_PD_PE_SINK */ + +static inline int pd_handle_tcp_event_bist_cm2(struct pd_port *pd_port) +{ + uint32_t bist = BDO_MODE_CARRIER2; + + if (!pd_check_pe_state_ready(pd_port)) + return TCP_DPM_RET_DENIED_NOT_READY; + + pd_send_sop_data_msg(pd_port, PD_DATA_BIST, 1, &bist); + return TCP_DPM_RET_SENT; +} + +#ifdef CONFIG_USB_PD_REV30 + +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE +static inline int pd_handle_tcp_event_get_source_cap_ext( + struct pd_port *pd_port) +{ + switch (pd_port->pe_state_curr) { + case PE_SNK_READY: + PE_TRANSIT_STATE(pd_port, PE_SNK_GET_SOURCE_CAP_EXT); + return TCP_DPM_RET_SENT; + +#ifdef CONFIG_USB_PD_PR_SWAP + case PE_SRC_READY: + if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) { + PE_TRANSIT_STATE(pd_port, + PE_DR_SRC_GET_SOURCE_CAP_EXT); + return TCP_DPM_RET_SENT; + } +#endif /* CONFIG_USB_PD_PR_SWAP */ + return TCP_DPM_RET_DENIED_LOCAL_CAP; + } + + return TCP_DPM_RET_DENIED_NOT_READY; +} +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK +static inline int pd_handle_tcp_event_get_pps_status(struct pd_port *pd_port) +{ + if (pd_port->pe_state_curr != PE_SNK_READY) + return TCP_DPM_RET_DENIED_NOT_READY; + + PE_TRANSIT_STATE(pd_port, PE_SNK_GET_PPS_STATUS); + return TCP_DPM_RET_SENT; +} +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +static inline int pd_make_tcp_event_transit_ready( + struct pd_port *pd_port, uint8_t state) +{ + if (!pd_check_pe_state_ready(pd_port)) + return TCP_DPM_RET_DENIED_NOT_READY; + + PE_TRANSIT_STATE(pd_port, state); + return TCP_DPM_RET_SENT; +} + +static inline int pd_make_tcp_event_transit_ready2( + struct pd_port *pd_port, uint8_t snk_state, uint8_t src_state) +{ + switch (pd_port->pe_state_curr) { + +#ifdef CONFIG_USB_PD_PE_SINK + case PE_SNK_READY: + PE_TRANSIT_STATE(pd_port, snk_state); + return TCP_DPM_RET_SENT; +#endif /* CONFIG_USB_PD_PE_SINK */ + +#ifdef CONFIG_USB_PD_PE_SOURCE + case PE_SRC_READY: + PE_TRANSIT_STATE(pd_port, src_state); + return TCP_DPM_RET_SENT; +#endif /* CONFIG_USB_PD_PE_SOURCE */ + } + + return TCP_DPM_RET_DENIED_NOT_READY; +} + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL +static inline int pd_handle_tcp_event_alert(struct pd_port *pd_port) +{ + struct tcp_dpm_event *tcp_event = &pd_port->tcp_event; + + if (pd_get_curr_pd_event(pd_port)->msg_sec == PD_TCP_FROM_TCPM) + pd_port->pe_data.local_alert |= tcp_event->tcp_dpm_data.index; + + return pd_make_tcp_event_transit_ready2(pd_port, + PE_SNK_SEND_SINK_ALERT, PE_SRC_SEND_SOURCE_ALERT); +} +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ + +#endif /* CONFIG_USB_PD_REV30 */ + +static inline int pd_handle_tcp_event_hardreset(struct pd_port *pd_port) +{ + pe_transit_hard_reset_state(pd_port); + return TCP_DPM_RET_SENT; +} + +static inline int pd_handle_tcp_event_error_recovery(struct pd_port *pd_port) +{ + PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY); + return TCP_DPM_RET_SENT; +} + +static inline int pd_handle_tcp_dpm_event( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + int ret = TCP_DPM_RET_DENIED_UNKNOWN; + +#ifdef CONFIG_USB_PD_REV30 + if (pd_event->msg >= TCP_DPM_EVT_PD30_COMMAND + && pd_event->msg < TCP_DPM_EVT_VDM_COMMAND) { + if (!pd_check_rev30(pd_port)) + return TCP_DPM_RET_DENIED_PD_REV; + } +#endif /* CONFIG_USB_PD_REV30 */ + + switch (pd_event->msg) { + default: + break; + case TCP_DPM_EVT_PR_SWAP_AS_SNK: + case TCP_DPM_EVT_PR_SWAP_AS_SRC: +#ifdef CONFIG_USB_PD_PR_SWAP + ret = pd_handle_tcp_event_pr_swap(pd_port, + pd_event->msg - TCP_DPM_EVT_PR_SWAP_AS_SNK); +#endif /* CONFIG_USB_PD_PR_SWAP */ + break; + + case TCP_DPM_EVT_DR_SWAP_AS_UFP: + case TCP_DPM_EVT_DR_SWAP_AS_DFP: +#ifdef CONFIG_USB_PD_DR_SWAP + ret = pd_handle_tcp_event_dr_swap(pd_port, + pd_event->msg - TCP_DPM_EVT_DR_SWAP_AS_UFP); +#endif /* CONFIG_USB_PD_DR_SWAP */ + break; + + case TCP_DPM_EVT_VCONN_SWAP_OFF: + case TCP_DPM_EVT_VCONN_SWAP_ON: +#ifdef CONFIG_USB_PD_VCONN_SWAP + ret = pd_handle_tcp_event_vconn_swap(pd_port, + pd_event->msg - TCP_DPM_EVT_VCONN_SWAP_OFF); +#endif /* CONFIG_USB_PD_VCONN_SWAP */ + break; + + case TCP_DPM_EVT_GOTOMIN: +#ifdef CONFIG_USB_PD_PE_SOURCE + ret = pd_handle_tcp_event_gotomin(pd_port); +#endif /* CONFIG_USB_PD_PE_SOURCE */ + break; + case TCP_DPM_EVT_SOFTRESET: + ret = pd_handle_tcp_event_softreset(pd_port); + break; + + case TCP_DPM_EVT_CABLE_SOFTRESET: +#ifdef CONFIG_PD_DFP_RESET_CABLE + ret = pd_handle_tcp_event_cable_softreset(pd_port); +#endif /* CONFIG_PD_DFP_RESET_CABLE */ + break; + + case TCP_DPM_EVT_GET_SOURCE_CAP: + ret = pd_handle_tcp_event_get_source_cap(pd_port); + break; + + case TCP_DPM_EVT_GET_SINK_CAP: + ret = pd_handle_tcp_event_get_sink_cap(pd_port); + break; + +#ifdef CONFIG_USB_PD_PE_SINK + case TCP_DPM_EVT_REQUEST: + ret = pd_handle_tcp_event_request(pd_port); + break; + case TCP_DPM_EVT_REQUEST_EX: + ret = pd_handle_tcp_event_request(pd_port); + break; + case TCP_DPM_EVT_REQUEST_AGAIN: + ret = pd_handle_tcp_event_request(pd_port); + break; +#endif /* CONFIG_USB_PD_PE_SINK */ + + case TCP_DPM_EVT_BIST_CM2: + ret = pd_handle_tcp_event_bist_cm2(pd_port); + break; + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE + case TCP_DPM_EVT_GET_SOURCE_CAP_EXT: + ret = pd_handle_tcp_event_get_source_cap_ext(pd_port); + break; +#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE + case TCP_DPM_EVT_GET_STATUS: + ret = pd_make_tcp_event_transit_ready2(pd_port, + PE_SNK_GET_SOURCE_STATUS, PE_SRC_GET_SINK_STATUS); + break; +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE + case TCP_DPM_EVT_GET_COUNTRY_CODE: + ret = pd_make_tcp_event_transit_ready( + pd_port, PE_GET_COUNTRY_CODES); + break; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + case TCP_DPM_EVT_GET_PPS_STATUS: + ret = pd_handle_tcp_event_get_pps_status(pd_port); + break; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + case TCP_DPM_EVT_ALERT: + ret = pd_handle_tcp_event_alert(pd_port); + break; +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ + +#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE + case TCP_DPM_EVT_GET_COUNTRY_INFO: + ret = pd_make_tcp_event_transit_ready( + pd_port, PE_GET_COUNTRY_INFO); + break; +#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE + case TCP_DPM_EVT_GET_BAT_CAP: + ret = pd_make_tcp_event_transit_ready( + pd_port, PE_GET_BATTERY_CAP); + break; +#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE + case TCP_DPM_EVT_GET_BAT_STATUS: + ret = pd_make_tcp_event_transit_ready( + pd_port, PE_GET_BATTERY_STATUS); + break; +#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE + case TCP_DPM_EVT_GET_MFRS_INFO: + ret = pd_make_tcp_event_transit_ready( + pd_port, PE_GET_MANUFACTURER_INFO); + break; +#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */ +#endif /* CONFIG_USB_PD_REV30 */ + + case TCP_DPM_EVT_HARD_RESET: + ret = pd_handle_tcp_event_hardreset(pd_port); + break; + + case TCP_DPM_EVT_ERROR_RECOVERY: + ret = pd_handle_tcp_event_error_recovery(pd_port); + break; + } + + return ret; +} + +bool pd_process_event_tcp(struct pd_port *pd_port, struct pd_event *pd_event) +{ + int ret = pd_handle_tcp_dpm_event(pd_port, pd_event); + + pd_notify_tcp_event_1st_result(pd_port, ret); + return ret == TCP_DPM_RET_SENT; +} diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_vcs.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_vcs.c new file mode 100644 index 000000000000..f2d94fab2bfe --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_vcs.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event For VCS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" + +#ifdef CONFIG_USB_PD_VCONN_SWAP +/* DPM Event reactions */ + +DECL_PE_STATE_TRANSITION(PD_DPM_MSG_ACK) = { + { PE_VCS_EVALUATE_SWAP, PE_VCS_ACCEPT_SWAP }, + { PE_VCS_TURN_ON_VCONN, PE_VCS_SEND_PS_RDY }, +}; +DECL_PE_STATE_REACTION(PD_DPM_MSG_ACK); + +/* + * [BLOCK] Porcess PD Ctrl MSG + */ + +static inline bool pd_process_ctrl_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + uint8_t vconn_state = pd_port->vconn_role ? + PE_VCS_WAIT_FOR_VCONN : PE_VCS_TURN_ON_VCONN; + + switch (pd_event->msg) { + case PD_CTRL_GOOD_CRC: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_VCS_ACCEPT_SWAP, vconn_state)) + return true; + break; + + case PD_CTRL_ACCEPT: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_VCS_SEND_SWAP, vconn_state)) + return true; + break; + + case PD_CTRL_PS_RDY: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_VCS_WAIT_FOR_VCONN, PE_VCS_TURN_OFF_VCONN)) + return true; + break; + } + + return false; +} + +/* + * [BLOCK] Porcess DPM MSG + */ + +static inline bool pd_process_dpm_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_DPM_ACK: + return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_ACK); + + case PD_DPM_NAK: + if (PE_MAKE_STATE_TRANSIT_SINGLE( + PE_VCS_EVALUATE_SWAP, PE_VCS_REJECT_VCONN_SWAP)) + return true; + break; + } + + return false; +} + +/* + * [BLOCK] Porcess Timer MSG + */ + +static inline bool pd_process_timer_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_TIMER_VCONN_ON: + if (PE_MAKE_STATE_TRANSIT_TO_HRESET(PE_VCS_WAIT_FOR_VCONN)) + return true; + break; + +#if CONFIG_USB_PD_VCONN_READY_TOUT != 0 + case PD_TIMER_VCONN_READY: + PE_STATE_DPM_ACK_IMMEDIATELY(pd_port); + break; +#endif + } + + return false; +} + +/* + * [BLOCK] Process Policy Engine's VCS Message + */ + +bool pd_process_event_vcs(struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->event_type) { + case PD_EVT_CTRL_MSG: + return pd_process_ctrl_msg(pd_port, pd_event); + + case PD_EVT_DPM_MSG: + return pd_process_dpm_msg(pd_port, pd_event); + + case PD_EVT_TIMER_MSG: + return pd_process_timer_msg(pd_port, pd_event); + + default: + return false; + } +} +#endif /* CONFIG_USB_PD_VCONN_SWAP */ diff --git a/drivers/usb/typec/mmi_tcpc/pd_process_evt_vdm.c b/drivers/usb/typec/mmi_tcpc/pd_process_evt_vdm.c new file mode 100644 index 000000000000..0be506135655 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/pd_process_evt_vdm.c @@ -0,0 +1,966 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Process Event For VDM + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/pd_core.h" +#include "inc/tcpci_event.h" +#include "inc/pd_process_evt.h" +#include "inc/pd_dpm_core.h" + +/* VDM reactions */ + +#define VDM_CMD_FLAG_CABLE_CMD (1<<0) +#define VDM_CMD_FLAG_SEND_BY_UFP (1<<1) +#define VDM_CMD_FLAG_SEND_BY_DFP (1<<2) +#define VDM_CMD_FLAG_RECV_BY_UFP (1<<3) +#define VDM_CMD_FLAG_RECV_BY_DFP (1<<4) +#define VDM_CMD_FLAG_PD30_DUPLEX (1<<5) + +struct vdm_state_transition { + uint8_t vdm_cmd; + uint8_t vdm_init_state; + uint8_t vdm_request_state; + uint8_t vdm_cmd_flags; +}; + +#define VDM_CMD_REACTION(cmd, init, req, flags) { \ + .vdm_cmd = cmd, \ + .vdm_init_state = init, \ + .vdm_request_state = req, \ + .vdm_cmd_flags = flags, \ +} + +#define VDM_DFP_CMD_REACTION(cmd, init, req) \ + VDM_CMD_REACTION(cmd, init, req, \ + VDM_CMD_FLAG_SEND_BY_DFP | VDM_CMD_FLAG_RECV_BY_UFP) + +#define VDM_UFP_CMD_REACTION(cmd, init, req) \ + VDM_CMD_REACTION(cmd, init, req, \ + VDM_CMD_FLAG_SEND_BY_UFP | VDM_CMD_FLAG_RECV_BY_DFP) + +#define VDM_CABLE_CMD_REACTION(cmd, init, req) \ + VDM_CMD_REACTION(cmd, init, req, \ + VDM_CMD_FLAG_SEND_BY_UFP | VDM_CMD_FLAG_SEND_BY_DFP \ + | VDM_CMD_FLAG_CABLE_CMD) + +#define VDM_DFP_CMD_REACTION_PD30(cmd, init, req) \ + VDM_CMD_REACTION(cmd, init, req, \ + VDM_CMD_FLAG_SEND_BY_DFP | VDM_CMD_FLAG_RECV_BY_UFP | \ + VDM_CMD_FLAG_PD30_DUPLEX) + +#define VDM_UFP_CMD_REACTION_PD30(cmd, init, req) \ + VDM_CMD_REACTION(cmd, init, req, \ + VDM_CMD_FLAG_SEND_BY_UFP | VDM_CMD_FLAG_RECV_BY_DFP | \ + VDM_CMD_FLAG_PD30_DUPLEX) + +static const struct vdm_state_transition pe_vdm_state_reactions[] = { + + VDM_DFP_CMD_REACTION_PD30(CMD_DISCOVER_IDENT, + PE_UFP_VDM_GET_IDENTITY, + PE_DFP_UFP_VDM_IDENTITY_REQUEST + ), + + VDM_DFP_CMD_REACTION_PD30(CMD_DISCOVER_SVID, + PE_UFP_VDM_GET_SVIDS, + PE_DFP_VDM_SVIDS_REQUEST + ), + + VDM_DFP_CMD_REACTION_PD30(CMD_DISCOVER_MODES, + PE_UFP_VDM_GET_MODES, + PE_DFP_VDM_MODES_REQUEST + ), + + VDM_DFP_CMD_REACTION(CMD_ENTER_MODE, + PE_UFP_VDM_EVALUATE_MODE_ENTRY, + PE_DFP_VDM_MODE_ENTRY_REQUEST + ), + + VDM_DFP_CMD_REACTION(CMD_EXIT_MODE, + PE_UFP_VDM_MODE_EXIT, + PE_DFP_VDM_MODE_EXIT_REQUEST + ), + + VDM_UFP_CMD_REACTION_PD30(CMD_ATTENTION, + PE_DFP_VDM_ATTENTION_REQUEST, + PE_UFP_VDM_ATTENTION_REQUEST + ), + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + VDM_DFP_CMD_REACTION(CMD_DP_STATUS, + PE_UFP_VDM_DP_STATUS_UPDATE, + PE_DFP_VDM_DP_STATUS_UPDATE_REQUEST + ), + + VDM_DFP_CMD_REACTION(CMD_DP_CONFIG, + PE_UFP_VDM_DP_CONFIGURE, + PE_DFP_VDM_DP_CONFIGURATION_REQUEST + ), +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + /* Only handle Timeout Case */ + VDM_DFP_CMD_REACTION_PD30(0, + PE_UFP_UVDM_RECV, + PE_DFP_UVDM_SEND + ), +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID + VDM_CABLE_CMD_REACTION(CMD_DISCOVER_IDENT, + 0, + PE_SRC_VDM_IDENTITY_REQUEST + ), +#endif /* CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID + VDM_CABLE_CMD_REACTION(CMD_DISCOVER_IDENT, + 0, + PE_DFP_CBL_VDM_IDENTITY_REQUEST + ), +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ +}; + +static inline bool pd_vdm_state_transit_rx(struct pd_port *pd_port, + const struct vdm_state_transition *state_transition) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (!pd_check_pe_state_ready(pd_port)) { + PE_DBG("670 : invalid, current status\n"); + return false; + } + + PE_TRANSIT_STATE(pd_port, state_transition->vdm_init_state); + return true; +} + +static inline bool pd_vdm_state_transit_tx(struct pd_port *pd_port, + uint8_t vdm_cmdt, const struct vdm_state_transition *state_transition) +{ + int ret_code = TCP_DPM_RET_VDM_NAK; + uint8_t curr_state = state_transition->vdm_request_state; + uint8_t next_state = curr_state + 1; + + switch (vdm_cmdt) { + + case CMDT_RSP_NAK: + if (curr_state != PE_DFP_VDM_MODE_EXIT_REQUEST) + next_state = curr_state+2; + break; + + case CMDT_RSP_BUSY: + if (curr_state == PE_DFP_VDM_MODE_EXIT_REQUEST) + next_state = pe_get_curr_hard_reset_state(pd_port); + else + next_state = curr_state+2; + break; + + default: + ret_code = TCP_DPM_RET_VDM_ACK; + break; + } + + pd_notify_tcp_vdm_event_2nd_result(pd_port, ret_code); + PE_TRANSIT_STATE(pd_port, next_state); + return true; +} + +bool vdm_is_state_transition_available(struct pd_port *pd_port, + bool recv, const struct vdm_state_transition *state_transition) +{ + uint8_t shift = 1; + uint8_t vdm_cmd_flags = state_transition->vdm_cmd_flags; + + if (pd_check_rev30(pd_port) && + (vdm_cmd_flags & VDM_CMD_FLAG_PD30_DUPLEX)) { + return true; + } + + if (pd_port->data_role == PD_ROLE_DFP) + shift += 1; + + if (recv) + shift += 2; + + return vdm_cmd_flags & (1 << shift); +} + +static bool pd_vdm_state_transit( + struct pd_port *pd_port, uint8_t vdm_cmdt, + const struct vdm_state_transition *state_transition) +{ + uint8_t curr_state; + + curr_state = pd_port->pe_state_curr; + + if (curr_state == PE_UFP_VDM_ATTENTION_REQUEST) { + VDM_STATE_DPM_INFORMED(pd_port); + pd_notify_tcp_vdm_event_2nd_result( + pd_port, TCP_DPM_RET_TIMEOUT); + return false; + } + + if (vdm_cmdt == CMDT_INIT) { /* Recv */ + if (!vdm_is_state_transition_available( + pd_port, true, state_transition)) + return false; + + return pd_vdm_state_transit_rx(pd_port, state_transition); + } + + /* Send */ + if (!vdm_is_state_transition_available( + pd_port, false, state_transition)) + return false; + + if (curr_state != state_transition->vdm_request_state) + return false; + + return pd_vdm_state_transit_tx( + pd_port, vdm_cmdt, state_transition); +} + +enum { + VDM_STATE_TRANSIT_SOP_CMD = 0, + VDM_STATE_TRANSIT_SOP_PRIME_CMD = VDM_CMD_FLAG_CABLE_CMD, + VDM_STATE_TRANSIT_NAK = 2, + + VDM_STATE_TRANSIT_CHECK_TX = 3, +}; + +static bool pe_check_vdm_state_transit_valid( + struct pd_port *pd_port, uint8_t transit_type, uint8_t *vdm_cmdt, + const struct vdm_state_transition *state_transition) +{ + uint8_t curr_state; + uint8_t vdm_cmd; + uint8_t cable_cmd; + uint8_t vdm_cmd_flags; + uint32_t curr_vdm_hdr; + + curr_state = pd_port->pe_state_curr; + + if (transit_type == VDM_STATE_TRANSIT_NAK) { + if (curr_state != state_transition->vdm_request_state) + return false; + + *vdm_cmdt = CMDT_RSP_BUSY; + return true; + } else if (transit_type >= VDM_STATE_TRANSIT_CHECK_TX) { + return state_transition->vdm_request_state == transit_type; + } + + curr_vdm_hdr = pd_port->curr_vdm_hdr; + + vdm_cmd = PD_VDO_CMD(curr_vdm_hdr); + *vdm_cmdt = PD_VDO_CMDT(curr_vdm_hdr); + + if (state_transition->vdm_cmd != vdm_cmd) + return false; + + vdm_cmd_flags = state_transition->vdm_cmd_flags; + cable_cmd = vdm_cmd_flags & VDM_CMD_FLAG_CABLE_CMD; + + if (cable_cmd != transit_type) + return false; + + if (cable_cmd && curr_state != state_transition->vdm_request_state) + return false; + + return true; +} + +static bool pd_make_vdm_state_transit( + struct pd_port *pd_port, uint8_t transit_type) +{ + int i; + bool check_tx; + uint8_t vdm_cmdt; + const struct vdm_state_transition *state_transition; + + uint8_t nr_transition = ARRAY_SIZE(pe_vdm_state_reactions); + const struct vdm_state_transition *state_reaction = + pe_vdm_state_reactions; + + check_tx = transit_type >= VDM_STATE_TRANSIT_CHECK_TX; + + for (i = 0; i < nr_transition; i++) { + state_transition = &state_reaction[i]; + + if (!pe_check_vdm_state_transit_valid( + pd_port, transit_type, &vdm_cmdt, state_transition)) + continue; + + if (check_tx) { + return vdm_is_state_transition_available( + pd_port, false, state_transition); + } else { + return pd_vdm_state_transit( + pd_port, vdm_cmdt, state_transition); + } + } + + return false; +} + +static inline bool pd_make_vdm_state_transit_sop(struct pd_port *pd_port) +{ + return pd_make_vdm_state_transit( + pd_port, VDM_STATE_TRANSIT_SOP_CMD); +} + +static inline bool pd_make_vdm_state_transit_cable(struct pd_port *pd_port) +{ + return pd_make_vdm_state_transit( + pd_port, VDM_STATE_TRANSIT_SOP_PRIME_CMD); +} + +static inline bool pd_make_vdm_state_transit_nak(struct pd_port *pd_port) +{ + return pd_make_vdm_state_transit( + pd_port, VDM_STATE_TRANSIT_NAK); +} + +/* Discover Cable ID */ + +#ifdef CONFIG_PD_DISCOVER_CABLE_ID +DECL_PE_STATE_TRANSITION(PD_DPM_MSG_DISCOVER_CABLE) = { +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID + { PE_SRC_STARTUP, PE_SRC_VDM_IDENTITY_REQUEST}, + { PE_SRC_DISCOVERY, PE_SRC_VDM_IDENTITY_REQUEST}, +#endif + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID + { PE_SRC_READY, PE_DFP_CBL_VDM_IDENTITY_REQUEST}, + { PE_SNK_READY, PE_DFP_CBL_VDM_IDENTITY_REQUEST}, +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ + +#ifdef CONFIG_PD_SRC_RESET_CABLE + { PE_SRC_CBL_SEND_SOFT_RESET, PE_SRC_VDM_IDENTITY_REQUEST}, +#endif /* CONFIG_PD_SRC_RESET_CABLE */ +}; +DECL_PE_STATE_REACTION(PD_DPM_MSG_DISCOVER_CABLE); +#endif /* CONFIG_PD_DISCOVER_CABLE_ID */ + +/* + * [BLOCK] Porcess Ctrl MSG + */ + +#ifdef CONFIG_USB_PD_ALT_MODE +#ifdef CONFIG_USB_PD_DBG_DP_UFP_U_AUTO_ATTENTION +static inline bool pd_ufp_u_auto_send_attention(struct pd_port *pd_port) +{ + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + if (dp_data->local_config != 0) { + PE_TRANSIT_STATE(pd_port, + PE_UFP_VDM_ATTENTION_REQUEST); + return true; + } + + return false; +} +#endif /* CONFIG_USB_PD_DBG_DP_UFP_U_AUTO_ATTENTION */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +static inline bool pd_process_ctrl_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + struct pe_data *pe_data = &pd_port->pe_data; + + /* VDM Only handle CtrlMsg = GoodCRC */ + + if (pd_event->msg != PD_CTRL_GOOD_CRC) + return false; + + if (pe_data->vdm_state_flags & + VDM_STATE_FLAG_ENABLE_VDM_RESPONSE_TIMER) + pd_enable_timer(pd_port, pe_data->vdm_state_timer); + + switch (pd_port->pe_state_curr) { +#ifdef CONFIG_USB_PD_ALT_MODE +#ifdef CONFIG_USB_PD_DBG_DP_UFP_U_AUTO_ATTENTION + case PE_UFP_VDM_DP_CONFIGURE: + if (pd_ufp_u_auto_send_attention(pd_port)) + return true; + break; +#endif /* CONFIG_USB_PD_DBG_DP_UFP_U_AUTO_ATTENTION */ + + case PE_UFP_VDM_ATTENTION_REQUEST: + pd_notify_tcp_vdm_event_2nd_result( + pd_port, TCP_DPM_RET_VDM_ACK); + break; +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID + case PE_SRC_VDM_IDENTITY_REQUEST: + pe_data->power_cable_present = true; + return false; +#endif /* CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID + case PE_DFP_CBL_VDM_IDENTITY_REQUEST: + pe_data->power_cable_present = true; + return false; +#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + case PE_DFP_UVDM_SEND: + if (!pd_port->uvdm_wait_resp) { + PE_TRANSIT_STATE(pd_port, PE_DFP_UVDM_ACKED); + return true; + } + break; +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + } + + if (pe_data->vdm_state_flags + & VDM_STATE_FLAG_BACK_READY_IF_RECV_GOOD_CRC) { + pe_transit_ready_state(pd_port); + return true; + } + + return false; +} + +/* + * [BLOCK] Porcess Custom MSG (SVDM/UVDM) + */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + +bool pd_process_custom_vdm(struct pd_port *pd_port, bool svdm) +{ + bool recv = false; + + if (pd_port->data_role == PD_ROLE_UFP) + recv = true; + + if (pd_check_rev30(pd_port) && svdm) + recv = true; + + if (pd_check_pe_state_ready(pd_port)) { + if (recv) { + PE_TRANSIT_STATE(pd_port, PE_UFP_UVDM_RECV); + return true; + } + } else if (pd_port->pe_state_curr == PE_DFP_UVDM_SEND) { + PE_TRANSIT_STATE(pd_port, PE_DFP_UVDM_ACKED); + return true; + } + + pd_put_dpm_event(pd_port, PD_DPM_NOT_SUPPORT); + return false; +} + +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +static inline bool pd_process_uvdm( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + +#ifdef CONFIG_USB_PD_UVDM + /* support sop only */ + if (pd_event->pd_msg->frame_type != TCPC_TX_SOP) + return false; + + if (pd_process_custom_vdm(pd_port, false)) + return true; +#else + pd_put_dpm_event(pd_port, PD_DPM_NOT_SUPPORT); +#endif /* CONFIG_USB_PD_UVDM */ + + /* TODO: Reply Not_Supported Message*/ + PE_DBG("659 : Invalid UVDM\n"); + return false; +} + +/* + * [BLOCK] Porcess Data MSG (VDM) + */ + +#if (PE_EVT_INFO_VDM_DIS == 0) +static const char * const pe_vdm_cmd_name[] = { + "DiscoverID", + "DiscoverSVID", + "DiscoverMode", + "EnterMode", + "ExitMode", + "Attention", +}; + +#if PE_INFO_ENABLE +static const char *const pe_vdm_cmd_type_name[] = { + "INIT", + "ACK", + "NACK", + "BUSY", +}; +#endif /* PE_INFO_ENABLE */ + +static inline const char *assign_vdm_cmd_name(uint8_t cmd) +{ + if (cmd <= ARRAY_SIZE(pe_vdm_cmd_name) && cmd != 0) { + cmd -= 1; + return pe_vdm_cmd_name[cmd]; + } + + return NULL; +} + +#ifdef CONFIG_USB_PD_ALT_MODE +static const char *const pe_vdm_dp_cmd_name[] = { + "DPStatus", + "DPConfig", +}; + +static inline const char *assign_vdm_dp_cmd_name(uint8_t cmd) +{ + if (cmd >= CMD_DP_STATUS) { + cmd -= CMD_DP_STATUS; + if (cmd < ARRAY_SIZE(pe_vdm_dp_cmd_name)) + return pe_vdm_dp_cmd_name[cmd]; + } + + return NULL; +} +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#endif /* if (PE_EVT_INFO_VDM_DIS == 0) */ + +static inline void print_vdm_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ +#if (PE_EVT_INFO_VDM_DIS == 0) + uint8_t cmd; + uint8_t cmd_type; + uint16_t svid; + const char *name = NULL; + uint32_t vdm_hdr = pd_port->curr_vdm_hdr; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + cmd = PD_VDO_CMD(vdm_hdr); + cmd_type = PD_VDO_CMDT(vdm_hdr); + svid = PD_VDO_VID(vdm_hdr); + + name = assign_vdm_cmd_name(cmd); + +#ifdef CONFIG_USB_PD_ALT_MODE + if (name == NULL && svid == USB_SID_DISPLAYPORT) + name = assign_vdm_dp_cmd_name(cmd); +#endif /* CONFIG_USB_PD_ALT_MODE */ + + if (name == NULL) + return; + +#if PE_INFO_ENABLE + if (cmd_type >= ARRAY_SIZE(pe_vdm_cmd_type_name)) + return; + + PE_INFO("%s:%s\n", name, pe_vdm_cmd_type_name[cmd_type]); +#endif /* PE_INFO_ENABLE */ + +#endif /* PE_EVT_INFO_VDM_DIS */ +} + +static inline bool pd_process_sop_vdm( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_make_vdm_state_transit_sop(pd_port)) + return true; + +#ifdef CONFIG_USB_PD_SVDM + if (pd_process_custom_vdm(pd_port, true)) + return true; +#endif /* CONFIG_USB_PD_SVDM */ + + PE_DBG("Unknown SVDM\n"); + return false; +} + +static inline bool pd_process_sop_prime_vdm( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + return pd_make_vdm_state_transit_cable(pd_port); +} + +static inline bool pd_process_data_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool ret = false; + uint32_t vdm_hdr; + struct pd_msg *pd_msg = pd_event->pd_msg; + + if (pd_event->msg != PD_DATA_VENDOR_DEF) + return ret; + + vdm_hdr = pd_msg->payload[0]; + pd_port->curr_vdm_hdr = vdm_hdr; + pd_port->curr_vdm_ops = PD_VDO_OPOS(vdm_hdr); + pd_port->curr_vdm_svid = PD_VDO_VID(vdm_hdr); + + if (!PD_VDO_SVDM(vdm_hdr)) + return pd_process_uvdm(pd_port, pd_event); + + /* From Port Partner, copy curr_state from pd_state */ + if (PD_VDO_CMDT(vdm_hdr) == CMDT_INIT) { + pd_port->pe_vdm_state = pd_port->pe_pd_state; + pd_port->pe_state_curr = pd_port->pe_pd_state; + +#if PE_DBG_RESET_VDM_DIS == 0 + PE_DBG("reset vdm_state\n"); +#endif /* if PE_DBG_RESET_VDM_DIS == 0 */ + } + + print_vdm_msg(pd_port, pd_event); + + if (pd_msg->frame_type == TCPC_TX_SOP_PRIME) + ret = pd_process_sop_prime_vdm(pd_port, pd_event); + else + ret = pd_process_sop_vdm(pd_port, pd_event); + + return ret; +} + +/* + * [BLOCK] Porcess PDM MSG + */ + +static inline bool pd_process_dpm_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + if (pd_event->msg != PD_DPM_ACK) + return false; + + if (pd_port->pe_data.vdm_state_flags & + VDM_STATE_FLAG_BACK_READY_IF_DPM_ACK) { + pe_transit_ready_state(pd_port); + return true; + } + + return false; +} + +/* + * [BLOCK] Porcess HW MSG + */ + +static inline bool pd_process_hw_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + struct pe_data *pe_data = &pd_port->pe_data; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_event->msg == PD_HW_TX_DISCARD && + (pe_data->vdm_discard_retry_count < 10)) { + PE_INFO("vdm_discard_retry\n"); + pe_data->vdm_discard_retry_flag = true; + pe_data->vdm_discard_retry_count++; + } + + switch (pd_event->msg) { + case PD_HW_TX_FAILED: + case PD_HW_TX_DISCARD: + return pd_make_vdm_state_transit_nak(pd_port); + + case PD_HW_RETRY_VDM: + if (pd_port->pe_data.vdm_state_timer) + return pd_make_vdm_state_transit_nak(pd_port); + PE_DBG("RetryVDM\n"); + return pd_process_sop_vdm(pd_port, pd_event); + } + + return false; +} + +/* + * [BLOCK] Porcess PE MSG + */ + +static inline bool pd_process_pe_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_PE_VDM_NOT_SUPPORT: + break; + + case PD_PE_VDM_RESET: + pd_port->pe_data.reset_vdm_state = true; + break; + + default: + return false; + } + + return pd_make_vdm_state_transit_nak(pd_port); +} + +/* + * [BLOCK] Porcess Timer MSG + */ + +static inline bool pd_process_timer_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { + case PD_TIMER_VDM_MODE_ENTRY: + case PD_TIMER_VDM_MODE_EXIT: + case PD_TIMER_VDM_RESPONSE: + case PD_TIMER_UVDM_RESPONSE: + return pd_make_vdm_state_transit_nak(pd_port); + + default: + return false; + } +} + +/* + * [BLOCK] Porcess TCP MSG + */ + +const uint8_t tcp_vdm_evt_init_state[] = { + PE_DFP_UFP_VDM_IDENTITY_REQUEST, /* TCP_DPM_EVT_DISCOVER_ID */ + PE_DFP_VDM_SVIDS_REQUEST, /* TCP_DPM_EVT_DISCOVER_SVIDS */ + PE_DFP_VDM_MODES_REQUEST, /* TCP_DPM_EVT_DISCOVER_MODES */ + PE_DFP_VDM_MODE_ENTRY_REQUEST, /* TCP_DPM_EVT_ENTER_MODE */ + PE_DFP_VDM_MODE_EXIT_REQUEST, /* TCP_DPM_EVT_EXIT_MODE */ + PE_UFP_VDM_ATTENTION_REQUEST, /* TCP_DPM_EVT_ATTENTION */ + +#ifdef CONFIG_USB_PD_ALT_MODE + PE_UFP_VDM_ATTENTION_REQUEST, /* TCP_DPM_EVT_DP_ATTENTION */ +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + PE_DFP_VDM_DP_STATUS_UPDATE_REQUEST, /* TCP_DPM_EVT_DP_STATUS_UPDATE */ + PE_DFP_VDM_DP_CONFIGURATION_REQUEST, /* TCP_DPM_EVT_DP_CONFIG */ +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + PE_DFP_UVDM_SEND, /* TCP_DPM_EVT_UVDM */ +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ +}; + +static inline bool pd_process_tcp_cable_event( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + bool ret; + int tcp_ret; +#ifdef CONFIG_PD_DISCOVER_CABLE_ID + bool role_check = true; + + if (pd_check_rev30(pd_port)) + role_check = pd_port->vconn_role; + + if (role_check) { + ret = PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_DISCOVER_CABLE); + tcp_ret = ret ? TCP_DPM_RET_SENT : TCP_DPM_RET_DENIED_NOT_READY; + } else { + ret = false; + tcp_ret = TCP_DPM_RET_DENIED_WRONG_DATA_ROLE; + } +#else + ret = false; + tcp_ret = TCP_DPM_RET_DENIED_UNKNOWN; +#endif /* CONFIG_PD_DISCOVER_CABLE_ID */ + + pd_notify_tcp_event_1st_result(pd_port, tcp_ret); + return ret; +} + +#ifdef CONFIG_USB_PD_ALT_MODE + +static inline uint32_t tcpc_update_bits( + uint32_t var, uint32_t update, uint32_t mask) +{ + return (var & (~mask)) | (update & mask); +} + +static inline void pd_parse_tcp_dpm_evt_svdm(struct pd_port *pd_port) +{ + struct tcp_dpm_svdm_data *svdm_data = + &pd_port->tcp_event.tcp_dpm_data.svdm_data; + + pd_port->mode_svid = svdm_data->svid; + pd_port->mode_obj_pos = svdm_data->ops; +} + +static inline void pd_parse_tcp_dpm_evt_dp_status(struct pd_port *pd_port) +{ + struct tcp_dpm_dp_data *dp_data_tcp = + &pd_port->tcp_event.tcp_dpm_data.dp_data; + + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + pd_port->mode_svid = USB_SID_DISPLAYPORT; + dp_data->local_status = tcpc_update_bits( + dp_data->local_status, + dp_data_tcp->val, dp_data_tcp->mask); +} + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + +static inline void pd_parse_tcp_dpm_evt_dp_config(struct pd_port *pd_port) +{ + struct tcp_dpm_dp_data *dp_data_tcp = + &pd_port->tcp_event.tcp_dpm_data.dp_data; + struct dp_data *dp_data = pd_get_dp_data(pd_port); + + pd_port->mode_svid = USB_SID_DISPLAYPORT; + dp_data->local_config = tcpc_update_bits( + dp_data->local_config, dp_data_tcp->val, dp_data_tcp->mask); +} + +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM +static inline void pd_parse_tcp_dpm_evt_uvdm(struct pd_port *pd_port) +{ + struct tcp_dpm_custom_vdm_data *vdm_data = + &pd_port->tcp_event.tcp_dpm_data.vdm_data; + + pd_port->uvdm_cnt = vdm_data->cnt; + pd_port->uvdm_wait_resp = vdm_data->wait_resp; + memcpy(pd_port->uvdm_data, + vdm_data->vdos, sizeof(uint32_t) * vdm_data->cnt); + +#ifdef CONFIG_USB_PD_SVDM + if (pd_check_rev30(pd_port) && + (pd_port->uvdm_data[0] & VDO_SVDM_TYPE)) + pd_port->uvdm_data[0] |= VDO_SVDM_VERS(SVDM_REV20); +#endif /* CONFIG_USB_PD_SVDM */ +} +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +static inline void pd_parse_tcp_dpm_evt_from_tcpm( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + switch (pd_event->msg) { +#ifdef CONFIG_USB_PD_KEEP_SVIDS + case TCP_DPM_EVT_DISCOVER_SVIDS: + pd_port->pe_data.remote_svid_list.cnt = 0; + break; +#endif /* CONFIG_USB_PD_KEEP_SVIDS */ + +#ifdef CONFIG_USB_PD_ALT_MODE + case TCP_DPM_EVT_DISCOVER_MODES: + case TCP_DPM_EVT_ENTER_MODE: + case TCP_DPM_EVT_EXIT_MODE: + case TCP_DPM_EVT_ATTENTION: + pd_parse_tcp_dpm_evt_svdm(pd_port); + break; + + case TCP_DPM_EVT_DP_ATTENTION: + pd_parse_tcp_dpm_evt_dp_status(pd_port); + break; + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + case TCP_DPM_EVT_DP_STATUS_UPDATE: + pd_parse_tcp_dpm_evt_dp_status(pd_port); + break; + case TCP_DPM_EVT_DP_CONFIG: + pd_parse_tcp_dpm_evt_dp_config(pd_port); + break; +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + case TCP_DPM_EVT_UVDM: + pd_parse_tcp_dpm_evt_uvdm(pd_port); + break; +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + } +} + +static inline bool pd_check_tcp_msg_valid( + struct pd_port *pd_port, uint8_t new_state) +{ + return pd_make_vdm_state_transit(pd_port, new_state); +} + +static inline bool pd_process_tcp_msg( + struct pd_port *pd_port, struct pd_event *pd_event) +{ + uint8_t new_state; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_event->msg == TCP_DPM_EVT_DISCOVER_CABLE) + return pd_process_tcp_cable_event(pd_port, pd_event); + + if (!pd_check_pe_state_ready(pd_port)) { + pd_notify_tcp_event_1st_result( + pd_port, TCP_DPM_RET_DENIED_NOT_READY); + PE_DBG("skip vdm_request, not ready_state (%d)\n", + pd_port->pe_state_curr); + return false; + } + + new_state = pd_event->msg - TCP_DPM_EVT_DISCOVER_ID; + if (new_state >= ARRAY_SIZE(tcp_vdm_evt_init_state)) { + PD_BUG_ON(1); + return false; + } + + new_state = tcp_vdm_evt_init_state[new_state]; + + if (!pd_check_tcp_msg_valid(pd_port, new_state)) { + pd_notify_tcp_event_1st_result( + pd_port, TCP_DPM_RET_DENIED_WRONG_DATA_ROLE); + PE_DBG("skip vdm_request, WRONG DATA ROLE\n"); + return false; + } + + if (pd_event->msg_sec == PD_TCP_FROM_TCPM) + pd_parse_tcp_dpm_evt_from_tcpm(pd_port, pd_event); + + PE_TRANSIT_STATE(pd_port, new_state); + pd_notify_tcp_event_1st_result(pd_port, TCP_DPM_RET_SENT); + return true; +} + +/* + * [BLOCK] Process Policy Engine's VDM Message + */ + +bool pd_process_event_vdm(struct pd_port *pd_port, struct pd_event *pd_event) +{ + pd_port->pe_data.vdm_discard_retry_flag = false; + + switch (pd_event->event_type) { + case PD_EVT_CTRL_MSG: + return pd_process_ctrl_msg(pd_port, pd_event); + + case PD_EVT_DATA_MSG: + return pd_process_data_msg(pd_port, pd_event); + + case PD_EVT_DPM_MSG: + return pd_process_dpm_msg(pd_port, pd_event); + + case PD_EVT_HW_MSG: + return pd_process_hw_msg(pd_port, pd_event); + + case PD_EVT_PE_MSG: + return pd_process_pe_msg(pd_port, pd_event); + + case PD_EVT_TIMER_MSG: + return pd_process_timer_msg(pd_port, pd_event); + + case PD_EVT_TCP_MSG: + return pd_process_tcp_msg(pd_port, pd_event); + } + + return false; +} diff --git a/drivers/usb/typec/mmi_tcpc/rt-regmap.c b/drivers/usb/typec/mmi_tcpc/rt-regmap.c new file mode 100644 index 000000000000..66f9ae78cf5b --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/rt-regmap.c @@ -0,0 +1,1641 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 MediaTek Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inc/rt-regmap.h" +#define RT_REGMAP_VERSION "1.2.1_G" + +#define ERR_MSG_SIZE 128 +#define MAX_BYTE_SIZE 32 + +struct rt_regmap_ops { + int (*regmap_block_write)(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src); + int (*regmap_block_read)(struct rt_regmap_device *rd, u32 reg, + int bytes, void *dst); +}; + +enum { + RT_DBG_REG_ADDR, + RT_DBG_DATA, + RT_DBG_REGS, + RT_DBG_SYNC, + RT_DBG_ERROR, + RT_DBG_NAME, + RT_DBG_BLOCK, + RT_DBG_SIZE, + RT_DBG_DEVICE_ADDR, + RT_DBG_SUPPORT_MODE, + RT_DBG_IO_LOG, + RT_DBG_CACHE_MODE, + RT_DBG_REG_SIZE, + RT_DBG_MAX, +}; + +struct reg_index_offset { + int index; + int offset; +}; + +#ifdef CONFIG_DEBUG_FS +struct rt_debug_data { + struct reg_index_offset rio; + unsigned int reg_addr; + unsigned int reg_size; +}; + +struct rt_debug_st { + void *info; + int id; +}; +#endif /* CONFIG_DEBUG_FS */ + +/* rt_regmap_device + * + * Richtek regmap device. One for each rt_regmap. + * + */ +struct rt_regmap_device { + struct rt_regmap_properties props; + struct rt_regmap_fops *rops; + struct rt_regmap_ops regmap_ops; + struct device dev; + void *client; + struct semaphore semaphore; + struct semaphore write_mode_lock; + struct delayed_work rt_work; + int dev_addr; + unsigned char *alloc_data; + unsigned char **cache_data; + unsigned long *cached; + unsigned long *cache_dirty; + char *err_msg; + unsigned char error_occurred:1; + unsigned char regval[MAX_BYTE_SIZE]; + + int (*rt_block_write[4])(struct rt_regmap_device *rd, + const struct rt_register *rm, int size, + const unsigned char *wdata, int count, + int cache_idx, int cache_offset); +#ifdef CONFIG_DEBUG_FS + struct dentry *rt_den; + struct dentry *rt_debug_file[RT_DBG_MAX]; + struct rt_debug_st rtdbg_st[RT_DBG_MAX]; + struct dentry **rt_reg_file; + struct rt_debug_st **reg_st; + struct rt_debug_data dbg_data; +#endif /* CONFIG_DEBUG_FS */ +}; + +static struct reg_index_offset find_register_index( + const struct rt_regmap_device *rd, u32 reg) +{ + int i = 0, j = 0, unit = RT_1BYTE_MODE; + struct reg_index_offset rio = {-1, -1}; + const rt_register_map_t *rm = rd->props.rm; + + for (i = 0; i < rd->props.register_num; i++) { + if (reg == rm[i]->addr) { + rio.index = i; + rio.offset = 0; + break; + } + if (reg > rm[i]->addr && + (reg - rm[i]->addr) < rm[i]->size) { + rio.index = i; + for (j = 0; rd->props.group[j].mode != RT_DUMMY_MODE; + j++) { + if (reg >= rd->props.group[j].start && + reg <= rd->props.group[j].end) { + unit = rd->props.group[j].mode; + break; + } + } + rio.offset = (reg - rm[i]->addr) * unit; + } + } + return rio; +} + +static int rt_chip_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src); + +/* rt_regmap_cache_sync - sync all cache data to chip */ +void rt_regmap_cache_sync(struct rt_regmap_device *rd) +{ + int ret = 0, i = 0, j = 0; + const rt_register_map_t rm = NULL; + + down(&rd->semaphore); + for (i = 0; i < rd->props.register_num; i++) { + if (!rd->cache_dirty[i]) + continue; + rm = rd->props.rm[i]; + for (j = 0; j < rm->size; j++) { + if (!test_bit(j, &rd->cache_dirty[i])) + continue; + ret = rt_chip_block_write(rd, rm->addr + j, 1, + rd->cache_data[i] + j); + if (ret < 0) { + dev_notice(&rd->dev, + "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rm->addr + j); + goto err_cache_sync; + } + } + rd->cache_dirty[i] = 0; + } + dev_info(&rd->dev, "%s successfully\n", __func__); +err_cache_sync: + up(&rd->semaphore); +} +EXPORT_SYMBOL(rt_regmap_cache_sync); + +/* rt_regmap_cache_write_back - write current cache data to chip + * @rd: rt_regmap_device pointer. + * @reg: register address + */ +void rt_regmap_cache_write_back(struct rt_regmap_device *rd, u32 reg) +{ + int ret = 0, j = 0; + struct reg_index_offset rio = {-1, -1}; + const rt_register_map_t rm = NULL; + + rio = find_register_index(rd, reg); + if (rio.index < 0 || rio.offset != 0) { + dev_notice(&rd->dev, "%s reg 0x%02x is out of map\n", + __func__, reg); + return; + } + + down(&rd->semaphore); + if (!rd->cache_dirty[rio.index]) + goto out; + rm = rd->props.rm[rio.index]; + for (j = 0; j < rm->size; j++) { + if (!test_bit(j, &rd->cache_dirty[rio.index])) + continue; + ret = rt_chip_block_write(rd, rm->addr + j, 1, + rd->cache_data[rio.index] + j); + if (ret < 0) { + dev_notice(&rd->dev, + "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rm->addr + j); + goto err_cache_write_back; + } + } + rd->cache_dirty[rio.index] = 0; +out: + dev_info(&rd->dev, "%s successfully\n", __func__); +err_cache_write_back: + up(&rd->semaphore); +} +EXPORT_SYMBOL(rt_regmap_cache_write_back); + +/* rt_is_reg_volatile - check register map is volatile or not + * @rd: rt_regmap_device pointer. + * @reg: register address. + */ +int rt_is_reg_volatile(struct rt_regmap_device *rd, u32 reg) +{ + struct reg_index_offset rio = {-1, -1}; + const rt_register_map_t rm = NULL; + + rio = find_register_index(rd, reg); + if (rio.index < 0) { + dev_notice(&rd->dev, "%s reg 0x%02x is out of range\n", + __func__, reg); + return -EINVAL; + } + rm = rd->props.rm[rio.index]; + + return (rm->reg_type & RT_REG_TYPE_MASK) == RT_VOLATILE ? 1 : 0; +} +EXPORT_SYMBOL(rt_is_reg_volatile); + +/* rt_reg_regsize - get register map size for specific register + * @rd: rt_regmap_device pointer. + * @reg: register address + */ +int rt_get_regsize(struct rt_regmap_device *rd, u32 reg) +{ + struct reg_index_offset rio = {-1, -1}; + + rio = find_register_index(rd, reg); + if (rio.index < 0 || rio.offset != 0) { + dev_notice(&rd->dev, "%s reg 0x%02x is out of map\n", + __func__, reg); + return -EINVAL; + } + return rd->props.rm[rio.index]->size; +} +EXPORT_SYMBOL(rt_get_regsize); + +static void rt_work_func(struct work_struct *work) +{ + struct rt_regmap_device *rd = + container_of(work, struct rt_regmap_device, rt_work.work); + + dev_info(&rd->dev, "%s\n", __func__); + rt_regmap_cache_sync(rd); +} + +static int rt_chip_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src) +{ + if ((rd->props.rt_regmap_mode & RT_IO_BLK_MODE_MASK) == RT_IO_BLK_ALL || + (rd->props.rt_regmap_mode & RT_IO_BLK_MODE_MASK) == RT_IO_BLK_CHIP) + return -EPERM; + + return rd->rops->write_device(rd->client, reg, bytes, src); +} + +static int rt_chip_block_read(struct rt_regmap_device *rd, u32 reg, + int bytes, void *dst) +{ + return rd->rops->read_device(rd->client, reg, bytes, dst); +} + +static int rt_block_write(struct rt_regmap_device *rd, + const struct rt_register *rm, int size, + const unsigned char *wdata, int count, + int cache_idx, int cache_offset) +{ + int ret = 0, j = 0, change = 0; + + down(&rd->write_mode_lock); + for (j = cache_offset; j < cache_offset + size; j++, count++) { + ret = test_and_set_bit(j, &rd->cached[cache_idx]); + if (ret && (rm->reg_type & RT_WR_ONCE)) { + if (rd->cache_data[cache_idx][j] == + (wdata[count] & rm->wbit_mask[j])) + continue; + } + rd->cache_data[cache_idx][j] = wdata[count] & rm->wbit_mask[j]; + change++; + } + + if (!change) + goto out; + + ret = rt_chip_block_write(rd, rm->addr + cache_offset, size, + rd->cache_data[cache_idx] + cache_offset); + if (ret < 0) + dev_notice(&rd->dev, "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rm->addr + cache_offset); +out: + up(&rd->write_mode_lock); + return ret < 0 ? ret : 0; +} + +static int rt_block_write_blk_all(struct rt_regmap_device *rd, + const struct rt_register *rm, int size, + const unsigned char *wdata, int count, + int cache_idx, int cache_offset) +{ + return 0; +} + +static int rt_block_write_blk_cache(struct rt_regmap_device *rd, + const struct rt_register *rm, int size, + const unsigned char *wdata, int count, + int cache_idx, int cache_offset) +{ + int ret = 0; + + down(&rd->write_mode_lock); + ret = rt_chip_block_write(rd, rm->addr + cache_offset, size, + &wdata[count]); + up(&rd->write_mode_lock); + if (ret < 0) + dev_notice(&rd->dev, "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rm->addr + cache_offset); + return ret < 0 ? ret : 0; +} + +static int rt_block_write_blk_chip(struct rt_regmap_device *rd, + const struct rt_register *rm, int size, + const unsigned char *wdata, int count, + int cache_idx, int cache_offset) +{ + int ret = 0, j = 0; + + down(&rd->write_mode_lock); + for (j = cache_offset; j < cache_offset + size; j++, count++) { + ret = test_and_set_bit(j, &rd->cached[cache_idx]); + if (ret && (rm->reg_type & RT_WR_ONCE)) { + if (rd->cache_data[cache_idx][j] == + (wdata[count] & rm->wbit_mask[j])) + continue; + } + rd->cache_data[cache_idx][j] = wdata[count] & rm->wbit_mask[j]; + set_bit(j, &rd->cache_dirty[cache_idx]); + } + up(&rd->write_mode_lock); + return 0; +} + +static int (*rt_block_map[])(struct rt_regmap_device *rd, + const struct rt_register *rm, int size, + const unsigned char *wdata, int count, + int cache_idx, int cache_offset) = { + &rt_block_write, + &rt_block_write_blk_all, + &rt_block_write_blk_cache, + &rt_block_write_blk_chip, +}; + +static int _rt_cache_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src, bool asyn) +{ + int ret = 0, i = 0, j = 0, count = 0, size = 0; + struct reg_index_offset rio = {-1, -1}; + const rt_register_map_t rm = NULL; + const unsigned char *wdata = src; + unsigned char wri_data[128], blk_index = 0; + + rio = find_register_index(rd, reg); + if (rio.index < 0) { + dev_notice(&rd->dev, "%s reg 0x%02x is out of range\n", + __func__, reg); + return -EINVAL; + } + + for (i = rio.index, j = rio.offset, count = 0; + i < rd->props.register_num && count < bytes; + i++, j = 0, count += size) { + rm = rd->props.rm[i]; + size = (bytes - count) <= (rm->size - j) ? + (bytes - count) : (rm->size - j); + if ((rm->reg_type & RT_REG_TYPE_MASK) == RT_VOLATILE) { + ret = rt_chip_block_write(rd, rm->addr + j, size, + &wdata[count]); + } else if (asyn) { + ret = rd->props.rt_regmap_mode & RT_IO_BLK_MODE_MASK; + if (ret == RT_IO_BLK_ALL || ret == RT_IO_BLK_CACHE) { + dev_notice(&rd->dev, "%s ret = %d\n", + __func__, ret); + ret = -EPERM; + goto err_cache_block_write; + } + + ret = rt_block_write_blk_chip + (rd, rm, size, wdata, count, i, j); + } else { + blk_index = (rd->props.rt_regmap_mode & + RT_IO_BLK_MODE_MASK) >> 3; + + ret = rd->rt_block_write[blk_index] + (rd, rm, size, wdata, count, i, j); + } + if (ret < 0) { + dev_notice(&rd->dev, + "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rm->addr + j); + goto err_cache_block_write; + } + } + if (rd->props.io_log_en) { + j = 0; + for (i = 0; i < count; i++) { + ret = snprintf(wri_data + j, sizeof(wri_data) - j, + "%02x,", wdata[i]); + if ((ret < 0) || (ret >= sizeof(wri_data) - j)) + return -EINVAL; + + j += ret; + } + dev_info(&rd->dev, "RT_REGMAP [WRITE] reg0x%02x [Data] %s\n", + reg, wri_data); + } + return 0; +err_cache_block_write: + return ret; +} + +static int rt_cache_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src) +{ + return _rt_cache_block_write(rd, reg, bytes, src, false); +} + +static int rt_asyn_cache_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src) +{ + int ret = 0; + + ret = _rt_cache_block_write(rd, reg, bytes, src, true); + if (ret >= 0) + mod_delayed_work(system_wq, &rd->rt_work, msecs_to_jiffies(1)); + return ret; +} + +static int rt_cache_block_read(struct rt_regmap_device *rd, u32 reg, + int bytes, void *dest) +{ + int ret = 0, i = 0, j = 0, count = 0, total_bytes = 0; + struct reg_index_offset rio = {-1, -1}; + const rt_register_map_t rm = NULL; + unsigned long mask = 0; + + rio = find_register_index(rd, reg); + if (rio.index < 0) { + dev_notice(&rd->dev, "%s reg 0x%02x is out of range\n", + __func__, reg); + return -EINVAL; + } + + rm = rd->props.rm[rio.index]; + + total_bytes += (rm->size - rio.offset); + + for (i = rio.index + 1; i < rd->props.register_num; i++) + total_bytes += rd->props.rm[i]->size; + + if (bytes > total_bytes) { + dev_notice(&rd->dev, "%s bytes %d is out of range\n", + __func__, bytes); + return -EINVAL; + } + + for (i = rio.index, j = rio.offset, count = 0; + i < rd->props.register_num && count < bytes; i++, j = 0) { + rm = rd->props.rm[i]; + mask = GENMASK(rm->size - 1, j); + if ((rd->cached[i] & mask) == mask) { + count += rm->size - j; + continue; + } + ret = rd->rops->read_device(rd->client, rm->addr, + rm->size, rd->regval); + if (ret < 0) { + dev_notice(&rd->dev, + "%s read device fail(%d) @ 0x%02x\n", + __func__, ret, rm->addr); + return ret; + } + for (; j < rm->size && count < bytes; j++, count++) { + if (test_bit(j, &rd->cached[i])) + continue; + rd->cache_data[i][j] = rd->regval[j]; + if ((rm->reg_type & RT_REG_TYPE_MASK) != RT_VOLATILE) + set_bit(j, &rd->cached[i]); + } + } + + if (rd->props.io_log_en) + dev_info(&rd->dev, "RT_REGMAP [READ] reg0x%02x\n", reg); + + memcpy(dest, &rd->cache_data[rio.index][rio.offset], bytes); + + return 0; +} + +static u32 cpu_to_chip(struct rt_regmap_device *rd, u32 cpu_data, int size) +{ + u32 chip_data = 0; + + if (rd->props.rt_format == RT_BIG_ENDIAN) { +#ifdef CONFIG_CPU_BIG_ENDIAN + chip_data = cpu_data << (4 - size) * 8; +#else + chip_data = cpu_to_be32(cpu_data); + chip_data >>= (4 - size) * 8; +#endif /* CONFIG_CPU_BIG_ENDIAN */ + } else { + chip_data = cpu_to_le32(cpu_data); + } + + return chip_data; +} + +static u32 chip_to_cpu(struct rt_regmap_device *rd, u32 chip_data, int size) +{ + u32 cpu_data = 0; + + if (rd->props.rt_format == RT_BIG_ENDIAN) { +#ifdef CONFIG_CPU_BIG_ENDIAN + cpu_data = chip_data >> ((4 - size) * 8); +#else + cpu_data = be32_to_cpu(chip_data); + cpu_data >>= (4 - size) * 8; +#endif /* CONFIG_CPU_BIG_ENDIAN */ + } else { + cpu_data = le32_to_cpu(chip_data); + } + + return cpu_data; +} + +/* _rt_regmap_reg_write - write data to specific register map + * only support 1, 2, 4 bytes regisetr map + * @rd: rt_regmap_device pointer. + * @rrd: rt_reg_data pointer. + */ +static int _rt_regmap_reg_write(struct rt_regmap_device *rd, + struct rt_reg_data *rrd, bool asyn) +{ + int ret = -ENOTSUPP, size = 0; + struct reg_index_offset rio = {-1, -1}; + const rt_register_map_t *rm = rd->props.rm; + u32 tmp_data = 0; + + rio = find_register_index(rd, rrd->reg); + if (rio.index < 0 || rio.offset != 0) { + dev_notice(&rd->dev, "%s reg 0x%02x is out of map\n", + __func__, rrd->reg); + return -EINVAL; + } + + size = rm[rio.index]->size; + if (size < 1 || size > 4) { + dev_notice(&rd->dev, "%s only support 1~4 bytes(%d)\n", + __func__, size); + return -EINVAL; + } + + tmp_data = cpu_to_chip(rd, rrd->rt_data.data_u32, size); + + down(&rd->semaphore); + ret = (asyn ? rt_asyn_cache_block_write : + rd->regmap_ops.regmap_block_write) + (rd, rrd->reg, size, &tmp_data); + up(&rd->semaphore); + if (ret < 0) + dev_notice(&rd->dev, "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rrd->reg); + return (ret < 0) ? ret : 0; +} + +int rt_regmap_reg_write(struct rt_regmap_device *rd, struct rt_reg_data *rrd, + u32 reg, const u32 data) +{ + rrd->reg = reg; + rrd->rt_data.data_u32 = data; + return _rt_regmap_reg_write(rd, rrd, false); +} +EXPORT_SYMBOL(rt_regmap_reg_write); + +int rt_asyn_regmap_reg_write(struct rt_regmap_device *rd, + struct rt_reg_data *rrd, u32 reg, const u32 data) +{ + rrd->reg = reg; + rrd->rt_data.data_u32 = data; + return _rt_regmap_reg_write(rd, rrd, true); +} +EXPORT_SYMBOL(rt_asyn_regmap_reg_write); + +/* _rt_regmap_reg_read - register read for specific register map + * only support 1, 2, 4 bytes register map. + * @rd: rt_regmap_device pointer. + * @rrd: rt_reg_data pointer. + */ +static int _rt_regmap_reg_read(struct rt_regmap_device *rd, + struct rt_reg_data *rrd) +{ + int ret = -ENOTSUPP, size = 0; + struct reg_index_offset rio = {-1, -1}; + const rt_register_map_t *rm = rd->props.rm; + u32 data = 0; + + rio = find_register_index(rd, rrd->reg); + if (rio.index < 0 || rio.offset != 0) { + dev_notice(&rd->dev, "%s reg 0x%02x is out of map\n", + __func__, rrd->reg); + return -EINVAL; + } + + size = rm[rio.index]->size; + if (size < 1 || size > 4) { + dev_notice(&rd->dev, "%s only support 1~4 bytes(%d)\n", + __func__, size); + return -EINVAL; + } + + down(&rd->semaphore); + ret = rd->regmap_ops.regmap_block_read(rd, rrd->reg, size, &data); + up(&rd->semaphore); + if (ret < 0) { + dev_notice(&rd->dev, "%s block read fail(%d) @ 0x%02x\n", + __func__, ret, rrd->reg); + goto out; + } + rrd->rt_data.data_u32 = chip_to_cpu(rd, data, size); +out: + return (ret < 0) ? ret : 0; +} + +int rt_regmap_reg_read(struct rt_regmap_device *rd, + struct rt_reg_data *rrd, u32 reg) +{ + rrd->reg = reg; + return _rt_regmap_reg_read(rd, rrd); +} +EXPORT_SYMBOL(rt_regmap_reg_read); + +/* _rt_regmap_update_bits - assign bits specific register map */ +static int _rt_regmap_update_bits(struct rt_regmap_device *rd, + struct rt_reg_data *rrd) +{ + + int ret = -ENOTSUPP, size = 0; + struct reg_index_offset rio = {-1, -1}; + const rt_register_map_t *rm = rd->props.rm; + u32 new = 0, old = 0; + bool change = false; + + rio = find_register_index(rd, rrd->reg); + if (rio.index < 0 || rio.offset != 0) { + dev_notice(&rd->dev, "%s reg 0x%02x is out of map\n", + __func__, rrd->reg); + return -EINVAL; + } + + size = rm[rio.index]->size; + if (size < 1 || size > 4) { + dev_notice(&rd->dev, "%s only support 1~4 bytes(%d)\n", + __func__, size); + return -EINVAL; + } + + down(&rd->semaphore); + ret = rd->regmap_ops.regmap_block_read(rd, rrd->reg, size, &old); + if (ret < 0) { + dev_notice(&rd->dev, "%s block read fail(%d) @ 0x%02x\n", + __func__, ret, rrd->reg); + goto out; + } + + old = chip_to_cpu(rd, old, size); + + new = (old & ~(rrd->mask)) | (rrd->rt_data.data_u32 & rrd->mask); + change = old != new; + if ((rm[rio.index]->reg_type & RT_WR_ONCE) && !change) + goto out; + + new = cpu_to_chip(rd, new, size); + + ret = rd->regmap_ops.regmap_block_write + (rd, rrd->reg, size, &new); + if (ret < 0) + dev_notice(&rd->dev, "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rrd->reg); +out: + up(&rd->semaphore); + return (ret < 0) ? ret : 0; +} + +int rt_regmap_update_bits(struct rt_regmap_device *rd, struct rt_reg_data *rrd, + u32 reg, u32 mask, u32 data) +{ + rrd->reg = reg; + rrd->mask = mask; + rrd->rt_data.data_u32 = data; + return _rt_regmap_update_bits(rd, rrd); +} +EXPORT_SYMBOL(rt_regmap_update_bits); + +/* rt_regmap_block_write - block write data to register + * @rd: rt_regmap_device pointer + * @reg: register address + * @bytes: length for write + * @src: source of write data + */ +int rt_regmap_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src) +{ + int ret = 0; + + down(&rd->semaphore); + ret = rd->regmap_ops.regmap_block_write(rd, reg, bytes, src); + up(&rd->semaphore); + return (ret < 0) ? ret : 0; +} +EXPORT_SYMBOL(rt_regmap_block_write); + +/* rt_asyn_regmap_block_write - asyn block write */ +int rt_asyn_regmap_block_write(struct rt_regmap_device *rd, u32 reg, + int bytes, const void *src) +{ + int ret = 0; + + down(&rd->semaphore); + ret = rt_asyn_cache_block_write(rd, reg, bytes, src); + up(&rd->semaphore); + return (ret < 0) ? ret : 0; +} +EXPORT_SYMBOL(rt_asyn_regmap_block_write); + +/* rt_regmap_block_read - block read data form register + * @rd: rt_regmap_device pointer + * @reg: register address + * @bytes: length for read + * @dst: destination for read data + */ +int rt_regmap_block_read(struct rt_regmap_device *rd, u32 reg, + int bytes, void *dst) +{ + int ret = 0; + + down(&rd->semaphore); + ret = rd->regmap_ops.regmap_block_read(rd, reg, bytes, dst); + up(&rd->semaphore); + return (ret < 0) ? ret : 0; +} +EXPORT_SYMBOL(rt_regmap_block_read); + +void rt_cache_getlasterror(struct rt_regmap_device *rd, char *buf, size_t size) +{ + int ret = 0; + + down(&rd->semaphore); + ret = snprintf(buf, size, "%s", rd->err_msg); + up(&rd->semaphore); + if ((ret < 0) || (ret >= size)) + dev_notice(&rd->dev, "%s snprintf fail(%d)\n", __func__, ret); +} +EXPORT_SYMBOL(rt_cache_getlasterror); + +void rt_cache_clrlasterror(struct rt_regmap_device *rd) +{ + int ret = 0; + + down(&rd->semaphore); + rd->error_occurred = 0; + rd->err_msg[0] = 0; + up(&rd->semaphore); + if ((ret < 0) || (ret >= ERR_MSG_SIZE)) + dev_notice(&rd->dev, "%s snprintf fail(%d)\n", __func__, ret); +} +EXPORT_SYMBOL(rt_cache_clrlasterror); + +/* initialize cache data from rt_register */ +static int rt_regmap_cache_init(struct rt_regmap_device *rd) +{ + int ret = 0, i = 0, j = 0, count = 0, bytes_num = 0; + const rt_register_map_t *rm = rd->props.rm; + + pr_info("%s\n", __func__); + + down(&rd->semaphore); + rd->cache_data = devm_kzalloc(&rd->dev, rd->props.register_num * + sizeof(*rd->cache_data), GFP_KERNEL); + rd->cached = devm_kzalloc(&rd->dev, rd->props.register_num * + sizeof(*rd->cached), GFP_KERNEL); + rd->cache_dirty = devm_kzalloc(&rd->dev, rd->props.register_num * + sizeof(*rd->cache_dirty), GFP_KERNEL); + + if (!rd->cache_data || !rd->cached || !rd->cache_dirty) { + ret = -ENOMEM; + goto out; + } + + if (rd->props.group == NULL) { + rd->props.group = devm_kzalloc(&rd->dev, + sizeof(*rd->props.group) * 2, + GFP_KERNEL); + if (!rd->props.group) { + ret = -ENOMEM; + goto out; + } + rd->props.group[0].start = 0; + rd->props.group[0].end = U32_MAX; + rd->props.group[0].mode = RT_1BYTE_MODE; + rd->props.group[1].mode = RT_DUMMY_MODE; + } + + for (i = 0; i < rd->props.register_num; i++) + bytes_num += rm[i]->size; + + rd->alloc_data = devm_kzalloc(&rd->dev, bytes_num, GFP_KERNEL); + if (!rd->alloc_data) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < rd->props.register_num; i++) { + rd->cache_data[i] = rd->alloc_data + count; + count += rm[i]->size; + } + + /* set 0xff writeable mask for NORMAL and RESERVE type */ + for (i = 0; i < rd->props.register_num; i++) { + if ((rm[i]->reg_type & RT_REG_TYPE_MASK) == RT_NORMAL || + (rm[i]->reg_type & RT_REG_TYPE_MASK) == RT_RESERVE) { + for (j = 0; j < rm[i]->size; j++) + rm[i]->wbit_mask[j] = 0xff; + } + } + + pr_info("%s successfully\n", __func__); +out: + up(&rd->semaphore); + return ret; +} + +/* rt_regmap_cache_reload - reload cache from chip */ +int rt_regmap_cache_reload(struct rt_regmap_device *rd) +{ + int i = 0; + + dev_info(&rd->dev, "%s\n", __func__); + down(&rd->semaphore); + for (i = 0; i < rd->props.register_num; i++) + rd->cache_dirty[i] = rd->cached[i] = 0; + up(&rd->semaphore); + return 0; +} +EXPORT_SYMBOL(rt_regmap_cache_reload); + +/* rt_regmap_add_debubfs - add user own debugfs node + * @rd: rt_regmap_devcie pointer. + * @name: a pointer to a string containing the name of the file to create. + * @mode: the permission that the file should have. + * @data: a pointer to something that the caller will want to get to later + * on. The inode.i_private pointer will point to this value on + * the open() call. + * @fops: a pointer to a struct file_operations that should be used for + * this file. + */ +int rt_regmap_add_debugfs(struct rt_regmap_device *rd, const char *name, + umode_t mode, void *data, + const struct file_operations *fops) +{ +#ifdef CONFIG_DEBUG_FS + struct dentry *den = NULL; + + den = debugfs_create_file(name, mode, rd->rt_den, data, fops); + if (!den) + return -EINVAL; +#endif /* CONFIG_DEBUG_FS */ + return 0; +} +EXPORT_SYMBOL(rt_regmap_add_debugfs); + +static void rt_regmap_set_cache_mode(struct rt_regmap_device *rd, + unsigned char mode) +{ + unsigned char mode_mask = mode & RT_CACHE_MODE_MASK; + + dev_info(&rd->dev, "%s mode_mask = %d\n", __func__, mode_mask); + + down(&rd->write_mode_lock); + if (mode_mask == RT_CACHE_WR_THROUGH) { + rt_regmap_cache_reload(rd); + rd->regmap_ops.regmap_block_write = rt_cache_block_write; + rd->regmap_ops.regmap_block_read = rt_cache_block_read; + } else if (mode_mask == RT_CACHE_WR_BACK) { + rt_regmap_cache_reload(rd); + rd->regmap_ops.regmap_block_write = rt_asyn_cache_block_write; + rd->regmap_ops.regmap_block_read = rt_cache_block_read; + } else if (mode_mask == RT_CACHE_DISABLE) { + rd->regmap_ops.regmap_block_write = rt_chip_block_write; + rd->regmap_ops.regmap_block_read = rt_chip_block_read; + } else { + dev_notice(&rd->dev, "%s invalid cache mode\n", __func__); + goto err_mode; + } + + rd->props.rt_regmap_mode &= ~RT_CACHE_MODE_MASK; + rd->props.rt_regmap_mode |= mode_mask; +err_mode: + up(&rd->write_mode_lock); +} + +#ifdef CONFIG_DEBUG_FS +struct dentry *rt_regmap_dir; + +#define erro_printf(rd, fmt, ...) \ +do { \ + int ret = 0; \ + size_t len = 0; \ + \ + dev_notice(&rd->dev, fmt, ##__VA_ARGS__); \ + down(&rd->semaphore); \ + len = strlen(rd->err_msg); \ + ret = snprintf(rd->err_msg + len, ERR_MSG_SIZE - len, \ + fmt, ##__VA_ARGS__); \ + rd->error_occurred = 1; \ + up(&rd->semaphore); \ + if ((ret < 0) || (ret >= ERR_MSG_SIZE - len)) \ + dev_notice(&rd->dev, "%s snprintf fail(%d)\n", \ + __func__, ret); \ +} while (0) + +static int get_parameters(char *buf, unsigned long *param, int num_of_par) +{ + int cnt = 0; + char *token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token) { + if (kstrtoul(token, 0, ¶m[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } else + return -EINVAL; + } + + return 0; +} + +static int get_data(const char *buf, size_t count, + unsigned char *data_buffer, unsigned int data_length) +{ + int i = 0, ptr = 0; + u8 value = 0; + char token[5] = {0}; + + token[0] = '0'; + token[1] = 'x'; + token[4] = 0; + if (buf[0] != '0' || buf[1] != 'x') + return -EINVAL; + + ptr = 2; + for (i = 0; (i < data_length) && (ptr + 2 <= count); i++) { + token[2] = buf[ptr++]; + token[3] = buf[ptr++]; + ptr++; + if (kstrtou8(token, 16, &value) != 0) + return -EINVAL; + data_buffer[i] = value; + } + return 0; +} + +static void rt_show_regs(struct rt_regmap_device *rd, struct seq_file *seq_file) +{ + int ret = 0, i = 0, j = 0; + const rt_register_map_t *rm = rd->props.rm; + + for (i = 0; i < rd->props.register_num; i++) { + down(&rd->semaphore); + ret = rd->regmap_ops.regmap_block_read(rd, rm[i]->addr, + rm[i]->size, rd->regval); + up(&rd->semaphore); + if (ret < 0) { + erro_printf(rd, "%s block read fail(%d) @ 0x%02x\n", + __func__, ret, rm[i]->addr); + break; + } + + if ((rm[i]->reg_type & RT_REG_TYPE_MASK) != RT_RESERVE) { + seq_printf(seq_file, "reg0x%02x:0x", rm[i]->addr); + for (j = 0; j < rm[i]->size; j++) + seq_printf(seq_file, "%02x,", rd->regval[j]); + seq_puts(seq_file, "\n"); + } else + seq_printf(seq_file, + "reg0x%02x:reserve\n", rm[i]->addr); + } +} + +static int general_read(struct seq_file *seq_file, void *_data) +{ + int ret = 0, i = 0, size = 0; + struct rt_debug_st *st = seq_file->private; + struct rt_regmap_device *rd = st->info; + unsigned char data = 0; + + switch (st->id) { + case RT_DBG_REG_ADDR: + seq_printf(seq_file, "0x%02x\n", rd->dbg_data.reg_addr); + break; + case RT_DBG_DATA: + if (rd->dbg_data.reg_size == 0) + rd->dbg_data.reg_size = 1; + size = rd->dbg_data.reg_size; + + down(&rd->semaphore); + if (rd->dbg_data.rio.index < 0) + ret = rt_chip_block_read(rd, rd->dbg_data.reg_addr, + size, rd->regval); + else + ret = rd->regmap_ops.regmap_block_read(rd, + rd->dbg_data.reg_addr, + size, rd->regval); + up(&rd->semaphore); + if (ret < 0) { + erro_printf(rd, "%s block read fail(%d) @ 0x%02x\n", + __func__, ret, rd->dbg_data.reg_addr); + break; + } + + seq_puts(seq_file, "0x"); + for (i = 0; i < size; i++) + seq_printf(seq_file, "%02x,", rd->regval[i]); + seq_puts(seq_file, "\n"); + break; + case RT_DBG_ERROR: + seq_puts(seq_file, "======== Error Message ========\n"); + seq_puts(seq_file, rd->error_occurred ? rd->err_msg : + "No Error\n"); + break; + case RT_DBG_REGS: + rt_show_regs(rd, seq_file); + break; + case RT_DBG_NAME: + seq_printf(seq_file, "%s\n", rd->props.aliases); + break; + case RT_DBG_SIZE: + seq_printf(seq_file, "%u\n", rd->dbg_data.reg_size); + break; + case RT_DBG_BLOCK: + data = rd->props.rt_regmap_mode & RT_IO_BLK_MODE_MASK; + if (data == RT_IO_PASS_THROUGH) + seq_puts(seq_file, "0 => IO_PASS_THROUGH\n"); + else if (data == RT_IO_BLK_ALL) + seq_puts(seq_file, "1 => IO_BLK_ALL\n"); + else if (data == RT_IO_BLK_CACHE) + seq_puts(seq_file, "2 => IO_BLK_CACHE\n"); + else if (data == RT_IO_BLK_CHIP) + seq_puts(seq_file, "3 => IO_BLK_CHIP\n"); + break; + case RT_DBG_DEVICE_ADDR: + seq_printf(seq_file, "0x%02x\n", rd->dev_addr); + break; + case RT_DBG_SUPPORT_MODE: + seq_puts(seq_file, " == BLOCK MODE ==\n"); + seq_puts(seq_file, "0 => IO_PASS_THROUGH\n"); + seq_puts(seq_file, "1 => IO_BLK_ALL\n"); + seq_puts(seq_file, "2 => IO_BLK_CACHE\n"); + seq_puts(seq_file, "3 => IO_BLK_CHIP\n"); + seq_puts(seq_file, " == CACHE MODE ==\n"); + seq_puts(seq_file, "0 => CACHE_WR_THROUGH\n"); + seq_puts(seq_file, "1 => CACHE_WR_BACK\n"); + seq_puts(seq_file, "2 => CACHE_DISABLE\n"); + break; + case RT_DBG_IO_LOG: + seq_printf(seq_file, "%d\n", rd->props.io_log_en); + break; + case RT_DBG_CACHE_MODE: + data = rd->props.rt_regmap_mode & RT_CACHE_MODE_MASK; + if (data == RT_CACHE_WR_THROUGH) + seq_puts(seq_file, "0 => CACHE_WR_THROUGH\n"); + else if (data == RT_CACHE_WR_BACK) + seq_puts(seq_file, "1 => CACHE_WR_BACK\n"); + else if (data == RT_CACHE_DISABLE) + seq_puts(seq_file, "2 => CACHE_DISABLE\n"); + break; + case RT_DBG_REG_SIZE: + size = rt_get_regsize(rd, rd->dbg_data.reg_addr); + seq_printf(seq_file, "%d\n", size); + break; + } + return 0; +} + +static int general_open(struct inode *inode, struct file *file) +{ + return single_open(file, general_read, inode->i_private); +} + +static ssize_t general_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret = 0; + struct rt_debug_st *st = + ((struct seq_file *)file->private_data)->private; + struct rt_regmap_device *rd = st->info; + struct reg_index_offset rio = {-1, -1}; + char lbuf[128]; + ssize_t res = 0; + unsigned int size = 0; + unsigned long param = 0; + + dev_info(&rd->dev, "%s @ %p, count = %u, pos = %llu\n", + __func__, ubuf, (unsigned int)count, *ppos); + *ppos = 0; + res = simple_write_to_buffer(lbuf, sizeof(lbuf) - 1, ppos, ubuf, count); + if (res <= 0) + return -EFAULT; + count = res; + lbuf[count] = '\0'; + + switch (st->id) { + case RT_DBG_REG_ADDR: + ret = get_parameters(lbuf, ¶m, 1); + if (ret < 0) + return ret; + rio = find_register_index(rd, param); + down(&rd->semaphore); + rd->dbg_data.rio = rio; + rd->dbg_data.reg_addr = param; + up(&rd->semaphore); + if (rio.index < 0) + erro_printf(rd, "%s reg 0x%02lx is out of range\n", + __func__, param); + break; + case RT_DBG_DATA: + if (rd->dbg_data.reg_size == 0) + rd->dbg_data.reg_size = 1; + size = rd->dbg_data.reg_size; + + if ((size - 1) * 3 + 5 != count) { + erro_printf(rd, + "%s wrong input length, size = %u, count = %u\n", + __func__, size, (unsigned int)count); + return -EINVAL; + } + + memset(rd->regval, 0, sizeof(rd->regval)); + ret = get_data(lbuf, count, rd->regval, size); + if (ret < 0) { + erro_printf(rd, "%s get data fail(%d)\n", + __func__, ret); + return ret; + } + + down(&rd->semaphore); + if (rd->dbg_data.rio.index < 0) + ret = rt_chip_block_write(rd, rd->dbg_data.reg_addr, + size, rd->regval); + else + ret = rd->regmap_ops.regmap_block_write(rd, + rd->dbg_data.reg_addr, + size, rd->regval); + up(&rd->semaphore); + if (ret < 0) { + erro_printf(rd, "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rd->dbg_data.reg_addr); + return ret; + } + break; + case RT_DBG_SYNC: + ret = get_parameters(lbuf, ¶m, 1); + if (ret < 0) + return ret; + if (param) + rt_regmap_cache_sync(rd); + break; + case RT_DBG_ERROR: + ret = get_parameters(lbuf, ¶m, 1); + if (ret < 0) + return ret; + if (param) + rt_cache_clrlasterror(rd); + break; + case RT_DBG_SIZE: + ret = get_parameters(lbuf, ¶m, 1); + if (ret < 0) + return ret; + if (param >= 1 && param <= MAX_BYTE_SIZE) { + down(&rd->semaphore); + rd->dbg_data.reg_size = param; + up(&rd->semaphore); + } else { + erro_printf(rd, "%s size(%lu) must be %d ~ %d\n", + __func__, param, 1, MAX_BYTE_SIZE); + return -EINVAL; + } + break; + case RT_DBG_BLOCK: + ret = get_parameters(lbuf, ¶m, 1); + if (ret < 0) + return ret; + if (param > 3) + param = 3; + param <<= 3; + + down(&rd->semaphore); + rd->props.rt_regmap_mode &= ~RT_IO_BLK_MODE_MASK; + rd->props.rt_regmap_mode |= param; + up(&rd->semaphore); + if (param == RT_IO_PASS_THROUGH) + rt_regmap_cache_sync(rd); + break; + case RT_DBG_IO_LOG: + ret = get_parameters(lbuf, ¶m, 1); + if (ret < 0) + return ret; + down(&rd->semaphore); + rd->props.io_log_en = !!param; + up(&rd->semaphore); + break; + case RT_DBG_CACHE_MODE: + ret = get_parameters(lbuf, ¶m, 1); + if (ret < 0) + return ret; + if (param > 2) + param = 2; + param <<= 1; + rt_regmap_set_cache_mode(rd, param); + break; + default: + return -EINVAL; + } + + return count; +} + +static const struct file_operations general_ops = { + .owner = THIS_MODULE, + .open = general_open, + .write = general_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define RT_CREATE_GENERAL_FILE(_id, _name, _mode) \ +{ \ + rd->rtdbg_st[_id].info = rd; \ + rd->rtdbg_st[_id].id = _id; \ + rd->rt_debug_file[_id] = debugfs_create_file(_name, _mode, dir, \ + &rd->rtdbg_st[_id], &general_ops); \ + if (!rd->rt_debug_file[_id]) \ + return -EINVAL; \ +} + +/* create general debugfs node */ +static int rt_create_general_debug(struct rt_regmap_device *rd, + struct dentry *dir) +{ + RT_CREATE_GENERAL_FILE(RT_DBG_REG_ADDR, "reg_addr", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_DATA, "data", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_REGS, "regs", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_SYNC, "sync", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_ERROR, "Error", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_NAME, "name", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_BLOCK, "block", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_SIZE, "size", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_DEVICE_ADDR, "device_addr", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_SUPPORT_MODE, "support_mode", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_IO_LOG, "io_log", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_CACHE_MODE, "cache_mode", 0444); + RT_CREATE_GENERAL_FILE(RT_DBG_REG_SIZE, "reg_size", 0444); + + return 0; +} + +static int eachreg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t eachreg_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret = 0, i = 0, j = 0; + struct rt_debug_st *st = file->private_data; + struct rt_regmap_device *rd = st->info; + const rt_register_map_t rm = rd->props.rm[st->id]; + char *lbuf = NULL; + const size_t lbuf_size = MAX_BYTE_SIZE * 3 + 11; + + lbuf = kzalloc(lbuf_size, GFP_KERNEL); + if (!lbuf) + return -ENOMEM; + + down(&rd->semaphore); + ret = rd->regmap_ops.regmap_block_read(rd, rm->addr, + rm->size, rd->regval); + up(&rd->semaphore); + if (ret < 0) { + dev_notice(&rd->dev, "%s block read fail(%d) @ 0x%02x\n", + __func__, ret, rm->size); + goto out; + } + + ret = snprintf(lbuf + j, lbuf_size - j, "reg0x%02x:0x", rm->addr); + if ((ret < 0) || (ret >= lbuf_size - j)) { + ret = -EINVAL; + goto out; + } + j += ret; + for (i = 0; i < rm->size; i++) { + ret = snprintf(lbuf + j, lbuf_size - j, "%02x,", rd->regval[i]); + if ((ret < 0) || (ret >= lbuf_size - j)) { + ret = -EINVAL; + goto out; + } + j += ret; + } + ret = snprintf(lbuf + j, lbuf_size - j, "\n"); + if ((ret < 0) || (ret >= lbuf_size - j)) { + ret = -EINVAL; + goto out; + } + j += ret; + ret = simple_read_from_buffer(ubuf, count, ppos, lbuf, strlen(lbuf)); +out: + kfree(lbuf); + return ret; +} + +static ssize_t eachreg_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret = 0; + struct rt_debug_st *st = file->private_data; + struct rt_regmap_device *rd = st->info; + const rt_register_map_t rm = rd->props.rm[st->id]; + char lbuf[128]; + ssize_t res = 0; + + if ((rm->size - 1) * 3 + 5 != count) { + dev_notice(&rd->dev, + "%s wrong input length, size = %u, count = %u\n", + __func__, rm->size, (unsigned int)count); + return -EINVAL; + } + + dev_info(&rd->dev, "%s @ %p, count = %u, pos = %llu\n", + __func__, ubuf, (unsigned int)count, *ppos); + *ppos = 0; + res = simple_write_to_buffer(lbuf, sizeof(lbuf) - 1, ppos, ubuf, count); + if (res <= 0) + return -EFAULT; + count = res; + lbuf[count] = '\0'; + + memset(rd->regval, 0, sizeof(rd->regval)); + ret = get_data(lbuf, count, rd->regval, rm->size); + if (ret < 0) { + dev_notice(&rd->dev, "%s get data fail(%d)\n", __func__, ret); + return ret; + } + + down(&rd->semaphore); + ret = rd->regmap_ops.regmap_block_write(rd, rm->addr, rm->size, + rd->regval); + up(&rd->semaphore); + if (ret < 0) { + dev_notice(&rd->dev, "%s block write fail(%d) @ 0x%02x\n", + __func__, ret, rm->addr); + return ret; + } + + return count; +} + +static const struct file_operations eachreg_ops = { + .open = eachreg_open, + .read = eachreg_read, + .write = eachreg_write, +}; + +/* create every register node at debugfs */ +static int rt_create_every_debug(struct rt_regmap_device *rd, + struct dentry *dir) +{ + int ret = 0, i = 0; + char buf[10]; + + rd->rt_reg_file = devm_kzalloc(&rd->dev, + rd->props.register_num * sizeof(*rd->rt_reg_file), GFP_KERNEL); + if (!rd->rt_reg_file) + return -ENOMEM; + + rd->reg_st = devm_kzalloc(&rd->dev, + rd->props.register_num * sizeof(*rd->reg_st), + GFP_KERNEL); + if (!rd->reg_st) + return -ENOMEM; + + for (i = 0; i < rd->props.register_num; i++) { + ret = snprintf(buf, sizeof(buf), + "reg0x%02x", rd->props.rm[i]->addr); + if ((ret < 0) || (ret >= sizeof(buf))) { + dev_notice(&rd->dev, "%s snprintf fail(%d)\n", + __func__, ret); + continue; + } + + rd->rt_reg_file[i] = devm_kzalloc(&rd->dev, + sizeof(*rd->rt_reg_file[i]), + GFP_KERNEL); + rd->reg_st[i] = devm_kzalloc(&rd->dev, + sizeof(*rd->reg_st[i]), + GFP_KERNEL); + if (!rd->rt_reg_file[i] || !rd->reg_st[i]) + return -ENOMEM; + + rd->reg_st[i]->info = rd; + rd->reg_st[i]->id = i; + rd->rt_reg_file[i] = debugfs_create_file(buf, 0444, dir, + rd->reg_st[i], + &eachreg_ops); + if (!rd->rt_reg_file[i]) + return -EINVAL; + } + + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +/* check the rt_register format is correct */ +static int rt_regmap_check(struct rt_regmap_device *rd) +{ + int i = 0; + const rt_register_map_t *rm = rd->props.rm; + + /* check name property */ + if (!rd->props.name) { + pr_notice("%s no name\n", __func__); + return -EINVAL; + } + + for (i = 0; i < rd->props.register_num; i++) { + /* check byte size, 1 byte ~ 32 bytes is valid */ + if (rm[i]->size < 1 || rm[i]->size > MAX_BYTE_SIZE) { + pr_notice("%s size(%d) must be %d ~ %d @ 0x%02x\n", + __func__, rm[i]->size, 1, MAX_BYTE_SIZE, + rm[i]->addr); + return -EINVAL; + } + } + + for (i = 0; i < rd->props.register_num - 1; i++) { + /* check register sequence */ + if (rm[i]->addr >= rm[i + 1]->addr) { + pr_info("%s sequence error @ 0x%02x\n", + __func__, rm[i]->addr); + } + } + + /* no default reg_addr and reister_map first addr is not 0x00 */ +#ifdef CONFIG_DEBUG_FS + if (!rd->dbg_data.reg_addr && rm[0]->addr) { + rd->dbg_data.reg_addr = rm[0]->addr; + rd->dbg_data.rio.index = 0; + rd->dbg_data.rio.offset = 0; + } +#endif /* CONFIG_DEBUG_FS */ + return 0; +} + +struct rt_regmap_device *rt_regmap_device_register_ex + (struct rt_regmap_properties *props, + struct rt_regmap_fops *rops, + struct device *parent, + void *client, int dev_addr, void *drvdata) +{ + int ret = 0, i = 0; + struct rt_regmap_device *rd = NULL; + + if (!props) { + pr_notice("%s rt_regmap_properties is NULL\n", __func__); + return NULL; + } + if (!rops) { + pr_notice("%s rt_regmap_fops is NULL\n", __func__); + return NULL; + } + + pr_info("%s name = %s\n", __func__, props->name); + rd = kzalloc(sizeof(*rd), GFP_KERNEL); + if (!rd) + return NULL; + + memcpy(&rd->props, props, sizeof(rd->props)); + rd->rops = rops; + rd->dev.parent = parent; + dev_set_drvdata(&rd->dev, drvdata); + rd->client = client; + sema_init(&rd->semaphore, 1); + sema_init(&rd->write_mode_lock, 1); + INIT_DELAYED_WORK(&rd->rt_work, rt_work_func); + rd->dev_addr = dev_addr; + + /* check rt_registe_map format */ + ret = rt_regmap_check(rd); + if (ret < 0) { + pr_notice("%s check fail(%d)\n", __func__, ret); + goto out; + } + + dev_set_name(&rd->dev, "rt_regmap_%s", rd->props.name); + ret = device_register(&rd->dev); + if (ret) { + pr_notice("%s device register fail(%d)\n", __func__, ret); + goto out; + } + + rd->err_msg = devm_kzalloc(&rd->dev, ERR_MSG_SIZE, GFP_KERNEL); + if (!rd->err_msg) + goto err_msgalloc; + + ret = rt_regmap_cache_init(rd); + if (ret < 0) { + pr_notice("%s init fail(%d)\n", __func__, ret); + goto err_cacheinit; + } + + for (i = 0; i <= 3; i++) + rd->rt_block_write[i] = rt_block_map[i]; + + rt_regmap_set_cache_mode(rd, rd->props.rt_regmap_mode); + +#ifdef CONFIG_DEBUG_FS + rd->rt_den = debugfs_create_dir(props->name, rt_regmap_dir); + if (rd->rt_den) { + ret = rt_create_general_debug(rd, rd->rt_den); + if (ret < 0) { + pr_notice("%s create general debug fail(%d)\n", + __func__, ret); + goto err_create_general_debug; + } + if (rd->props.rt_regmap_mode & RT_DBG_MODE_MASK) { + ret = rt_create_every_debug(rd, rd->rt_den); + if (ret < 0) { + pr_notice("%s create every debug fail(%d)\n", + __func__, ret); + goto err_create_every_debug; + } + } + } else { + pr_notice("%s debugfs create dir fail\n", __func__); + goto err_debug; + } +#endif /* CONFIG_DEBUG_FS */ + + return rd; + +#ifdef CONFIG_DEBUG_FS +err_create_every_debug: +err_create_general_debug: + debugfs_remove_recursive(rd->rt_den); +err_debug: +#endif /* CONFIG_DEBUG_FS */ +err_cacheinit: +err_msgalloc: + device_unregister(&rd->dev); +out: + kfree(rd); + return NULL; +} +EXPORT_SYMBOL(rt_regmap_device_register_ex); + +/* rt_regmap_device_unregister - unregister rt_regmap_device */ +void rt_regmap_device_unregister(struct rt_regmap_device *rd) +{ + if (!rd) + return; + down(&rd->semaphore); + rd->rops = NULL; + up(&rd->semaphore); +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(rd->rt_den); +#endif /* CONFIG_DEBUG_FS */ + device_unregister(&rd->dev); + kfree(rd); +} +EXPORT_SYMBOL(rt_regmap_device_unregister); + +int regmap_plat_init(void) +{ + pr_info("Init Richtek RegMap %s\n", RT_REGMAP_VERSION); +#ifdef CONFIG_DEBUG_FS + rt_regmap_dir = debugfs_create_dir("rt-regmap", NULL); + if (!rt_regmap_dir) { + pr_notice("%s debugfs create dir fail\n", __func__); + return -EINVAL; + } +#endif /* CONFIG_DEBUG_FS */ + return 0; +} + +void regmap_plat_exit(void) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove(rt_regmap_dir); +#endif /* CONFIG_DEBUG_FS */ +} + +MODULE_DESCRIPTION("Richtek regmap Driver"); +MODULE_AUTHOR("Jeff Chang "); +MODULE_VERSION(RT_REGMAP_VERSION); +MODULE_LICENSE("GPL"); + +/* Release Note + * 1.2.1 + * Fix the deadlock in rt_asyn_cache_block_write() + * + * 1.2.0 + * Revise memory allocation, code flow, and error handling + * + * 1.1.14 + * Fix Coverity by Mandatory's + */ diff --git a/drivers/usb/typec/mmi_tcpc/rt_pd_manager.c b/drivers/usb/typec/mmi_tcpc/rt_pd_manager.c new file mode 100644 index 000000000000..238b38aeb9c2 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/rt_pd_manager.c @@ -0,0 +1,1208 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Richtek RT PD Manager + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inc/tcpci_typec.h" + +#define RT_PD_MANAGER_VERSION "0.0.8_G" + +#define PROBE_CNT_MAX 3 +/* 10ms * 100 = 1000ms = 1s */ +#define USB_TYPE_POLLING_INTERVAL 10 +#define USB_TYPE_POLLING_CNT_MAX 100 + +enum dr { + DR_IDLE, + DR_DEVICE, + DR_HOST, + DR_DEVICE_TO_HOST, + DR_HOST_TO_DEVICE, + DR_MAX, +}; + +static char *dr_names[DR_MAX] = { + "Idle", "Device", "Host", "Device to Host", "Host to Device", +}; + +struct rt_pd_manager_data { + struct device *dev; + struct extcon_dev *extcon; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) + struct iio_channel **iio_channels; +#else + struct power_supply *usb_psy; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + struct delayed_work usb_dwork; + struct tcpc_device *tcpc; + struct notifier_block pd_nb; + enum dr usb_dr; + int usb_type_polling_cnt; + int sink_mv_new; + int sink_ma_new; + int sink_mv_old; + int sink_ma_old; + + struct typec_capability typec_caps; + struct typec_port *typec_port; + struct typec_partner *partner; + struct typec_partner_desc partner_desc; + struct usb_pd_identity partner_identity; +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) +#include +#define POWER_SUPPLY_TYPE_USB_FLOAT QTI_POWER_SUPPLY_TYPE_USB_FLOAT +#define POWER_SUPPLY_PD_INACTIVE QTI_POWER_SUPPLY_PD_INACTIVE +#define POWER_SUPPLY_PD_ACTIVE QTI_POWER_SUPPLY_PD_ACTIVE +#define POWER_SUPPLY_PD_PPS_ACTIVE QTI_POWER_SUPPLY_PD_PPS_ACTIVE + +enum iio_psy_property { + POWER_SUPPLY_PROP_PD_ACTIVE = 0, + POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED, + POWER_SUPPLY_PROP_PD_IN_HARD_RESET, + POWER_SUPPLY_PROP_PD_CURRENT_MAX, + POWER_SUPPLY_PROP_PD_VOLTAGE_MIN, + POWER_SUPPLY_PROP_PD_VOLTAGE_MAX, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_IIO_PROP_MAX, +}; + +static const char * const iio_channel_map[] = { + "pd_active", "pd_usb_suspend_supported", "pd_in_hard_reset", + "pd_current_max", "pd_voltage_min", "pd_voltage_max", "real_type", +}; + +static int smblib_get_prop(struct rt_pd_manager_data *rpmd, + enum iio_psy_property ipp, + union power_supply_propval *val) +{ + int ret = 0, value = 0; + + ret = iio_read_channel_processed(rpmd->iio_channels[ipp], &value); + if (ret < 0) { + dev_err(rpmd->dev, "%s fail(%d), ipp = %d\n", + __func__, ret, ipp); + return ret; + } + + val->intval = value; + return 0; +} + +static int smblib_set_prop(struct rt_pd_manager_data *rpmd, + enum iio_psy_property ipp, + const union power_supply_propval *val) +{ + int ret = 0; + + ret = iio_write_channel_raw(rpmd->iio_channels[ipp], val->intval); + if (ret < 0) { + dev_err(rpmd->dev, "%s fail(%d), ipp = %d\n", + __func__, ret, ipp); + return ret; + } + + return 0; +} +#else +static inline int smblib_get_prop(struct rt_pd_manager_data *rpmd, + enum power_supply_property psp, + union power_supply_propval *val) +{ + return power_supply_get_property(rpmd->usb_psy, psp, val); +} + +static inline int smblib_set_prop(struct rt_pd_manager_data *rpmd, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + return power_supply_set_property(rpmd->usb_psy, psp, val); +} +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + +static const unsigned int rpm_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static int extcon_init(struct rt_pd_manager_data *rpmd) +{ + int ret = 0; + + /* + * associate extcon with the dev as it could have a DT + * node which will be useful for extcon_get_edev_by_phandle() + */ + rpmd->extcon = devm_extcon_dev_allocate(rpmd->dev, rpm_extcon_cable); + if (IS_ERR(rpmd->extcon)) { + ret = PTR_ERR(rpmd->extcon); + dev_err(rpmd->dev, "%s extcon dev alloc fail(%d)\n", + __func__, ret); + goto out; + } + + ret = devm_extcon_dev_register(rpmd->dev, rpmd->extcon); + if (ret) { + dev_err(rpmd->dev, "%s extcon dev reg fail(%d)\n", + __func__, ret); + goto out; + } + + /* Support reporting polarity and speed via properties */ + extcon_set_property_capability(rpmd->extcon, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY); + extcon_set_property_capability(rpmd->extcon, EXTCON_USB, + EXTCON_PROP_USB_SS); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) + extcon_set_property_capability(rpmd->extcon, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_MED_HIGH_CURRENT); +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) */ + extcon_set_property_capability(rpmd->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY); + extcon_set_property_capability(rpmd->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS); +out: + return ret; +} + +static inline void stop_usb_host(struct rt_pd_manager_data *rpmd) +{ + extcon_set_state_sync(rpmd->extcon, EXTCON_USB_HOST, false); +} + +static inline void start_usb_host(struct rt_pd_manager_data *rpmd) +{ + union extcon_property_value val = {.intval = 0}; + + val.intval = tcpm_inquire_cc_polarity(rpmd->tcpc); + extcon_set_property(rpmd->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY, val); + + val.intval = 1; + extcon_set_property(rpmd->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS, val); + + extcon_set_state_sync(rpmd->extcon, EXTCON_USB_HOST, true); +} + +static inline void stop_usb_peripheral(struct rt_pd_manager_data *rpmd) +{ + extcon_set_state_sync(rpmd->extcon, EXTCON_USB, false); +} + +static inline void start_usb_peripheral(struct rt_pd_manager_data *rpmd) +{ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) + int rp = 0; +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) */ + union extcon_property_value val = {.intval = 0}; + + val.intval = tcpm_inquire_cc_polarity(rpmd->tcpc); + extcon_set_property(rpmd->extcon, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY, val); + + val.intval = 1; + extcon_set_property(rpmd->extcon, EXTCON_USB, EXTCON_PROP_USB_SS, val); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) + rp = tcpm_inquire_typec_remote_rp_curr(rpmd->tcpc); + val.intval = rp > 500 ? 1 : 0; + extcon_set_property(rpmd->extcon, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_MED_HIGH_CURRENT, val); +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) */ + extcon_set_state_sync(rpmd->extcon, EXTCON_USB, true); +} + +static void usb_dwork_handler(struct work_struct *work) +{ + int ret = 0; + struct delayed_work *dwork = to_delayed_work(work); + struct rt_pd_manager_data *rpmd = + container_of(dwork, struct rt_pd_manager_data, usb_dwork); + enum dr usb_dr = rpmd->usb_dr; + union power_supply_propval val = {.intval = 0}; + + if (usb_dr < DR_IDLE || usb_dr >= DR_MAX) { + dev_err(rpmd->dev, "%s invalid usb_dr = %d\n", + __func__, usb_dr); + return; + } + + dev_info(rpmd->dev, "%s %s\n", __func__, dr_names[usb_dr]); + + switch (usb_dr) { + case DR_IDLE: + case DR_MAX: + stop_usb_peripheral(rpmd); + stop_usb_host(rpmd); + break; + case DR_DEVICE: + ret = smblib_get_prop(rpmd, POWER_SUPPLY_PROP_REAL_TYPE, &val); + dev_info(rpmd->dev, "%s polling_cnt = %d, ret = %d type = %d\n", + __func__, ++rpmd->usb_type_polling_cnt, + ret, val.intval); + if (ret < 0 || val.intval == POWER_SUPPLY_TYPE_UNKNOWN) { + if (rpmd->usb_type_polling_cnt < + USB_TYPE_POLLING_CNT_MAX) + schedule_delayed_work(&rpmd->usb_dwork, + msecs_to_jiffies( + USB_TYPE_POLLING_INTERVAL)); + break; + } else if (val.intval != POWER_SUPPLY_TYPE_USB && + val.intval != POWER_SUPPLY_TYPE_USB_CDP && + val.intval != POWER_SUPPLY_TYPE_USB_FLOAT && + val.intval != POWER_SUPPLY_TYPE_USB_PD) + break; + case DR_HOST_TO_DEVICE: + stop_usb_host(rpmd); + start_usb_peripheral(rpmd); + break; + case DR_HOST: + case DR_DEVICE_TO_HOST: + stop_usb_peripheral(rpmd); + start_usb_host(rpmd); + break; + } +} + +static void pd_sink_set_vol_and_cur(struct rt_pd_manager_data *rpmd, + int mv, int ma, uint8_t type) +{ + const int micro_5v = 5000000; + unsigned long sel = 0; + union power_supply_propval val = {.intval = 0}; + + /* Charger plug-in first time */ + smblib_get_prop(rpmd, POWER_SUPPLY_PROP_PD_ACTIVE, &val); + if (val.intval == POWER_SUPPLY_PD_INACTIVE) { + val.intval = POWER_SUPPLY_PD_ACTIVE; + smblib_set_prop(rpmd, POWER_SUPPLY_PROP_PD_ACTIVE, &val); + } + + switch (type) { + case TCP_VBUS_CTRL_PD_HRESET: + case TCP_VBUS_CTRL_PD_PR_SWAP: + case TCP_VBUS_CTRL_PD_REQUEST: + set_bit(0, &sel); + set_bit(1, &sel); + val.intval = mv * 1000; + break; + case TCP_VBUS_CTRL_PD_STANDBY_UP: + set_bit(1, &sel); + val.intval = mv * 1000; + break; + case TCP_VBUS_CTRL_PD_STANDBY_DOWN: + set_bit(0, &sel); + val.intval = mv * 1000; + break; + default: + break; + } + if (val.intval < micro_5v) + val.intval = micro_5v; + if (test_bit(0, &sel)) + smblib_set_prop(rpmd, POWER_SUPPLY_PROP_PD_VOLTAGE_MIN, &val); + if (test_bit(1, &sel)) + smblib_set_prop(rpmd, POWER_SUPPLY_PROP_PD_VOLTAGE_MAX, &val); + + val.intval = ma * 1000; + smblib_set_prop(rpmd, POWER_SUPPLY_PROP_PD_CURRENT_MAX, &val); +} + +static int pd_tcp_notifier_call(struct notifier_block *nb, + unsigned long event, void *data) +{ + int ret = 0; + struct tcp_notify *noti = data; + struct rt_pd_manager_data *rpmd = + container_of(nb, struct rt_pd_manager_data, pd_nb); + uint8_t old_state = TYPEC_UNATTACHED, new_state = TYPEC_UNATTACHED; + enum typec_pwr_opmode opmode = TYPEC_PWR_MODE_USB; + uint32_t partner_vdos[VDO_MAX_NR]; + union power_supply_propval val = {.intval = 0}; + + switch (event) { + case TCP_NOTIFY_SINK_VBUS: + rpmd->sink_mv_new = noti->vbus_state.mv; + rpmd->sink_ma_new = noti->vbus_state.ma; + dev_info(rpmd->dev, "%s sink vbus %dmV %dmA type(0x%02X)\n", + __func__, rpmd->sink_mv_new, + rpmd->sink_ma_new, noti->vbus_state.type); + + if ((rpmd->sink_mv_new != rpmd->sink_mv_old) || + (rpmd->sink_ma_new != rpmd->sink_ma_old)) { + rpmd->sink_mv_old = rpmd->sink_mv_new; + rpmd->sink_ma_old = rpmd->sink_ma_new; + if (rpmd->sink_mv_new && rpmd->sink_ma_new) { + /* enable VBUS power path */ + } else { + /* disable VBUS power path */ + } + } + + if (noti->vbus_state.type & TCP_VBUS_CTRL_PD_DETECT) + pd_sink_set_vol_and_cur(rpmd, rpmd->sink_mv_new, + rpmd->sink_ma_new, + noti->vbus_state.type); + break; + case TCP_NOTIFY_SOURCE_VBUS: + dev_info(rpmd->dev, "%s source vbus %dmV\n", + __func__, noti->vbus_state.mv); + /* enable/disable OTG power output */ + break; + case TCP_NOTIFY_TYPEC_STATE: + old_state = noti->typec_state.old_state; + new_state = noti->typec_state.new_state; + if (old_state == TYPEC_UNATTACHED && + (new_state == TYPEC_ATTACHED_SNK || + new_state == TYPEC_ATTACHED_NORP_SRC || + new_state == TYPEC_ATTACHED_CUSTOM_SRC || + new_state == TYPEC_ATTACHED_DBGACC_SNK)) { + dev_info(rpmd->dev, + "%s Charger plug in, polarity = %d\n", + __func__, noti->typec_state.polarity); + /* + * start charger type detection, + * and enable device connection + */ + cancel_delayed_work_sync(&rpmd->usb_dwork); + rpmd->usb_dr = DR_DEVICE; + rpmd->usb_type_polling_cnt = 0; + schedule_delayed_work(&rpmd->usb_dwork, + msecs_to_jiffies( + USB_TYPE_POLLING_INTERVAL)); + typec_set_data_role(rpmd->typec_port, TYPEC_DEVICE); + typec_set_pwr_role(rpmd->typec_port, TYPEC_SINK); + typec_set_pwr_opmode(rpmd->typec_port, + noti->typec_state.rp_level - + TYPEC_CC_VOLT_SNK_DFT); + typec_set_vconn_role(rpmd->typec_port, TYPEC_SINK); + } else if ((old_state == TYPEC_ATTACHED_SNK || + old_state == TYPEC_ATTACHED_NORP_SRC || + old_state == TYPEC_ATTACHED_CUSTOM_SRC || + old_state == TYPEC_ATTACHED_DBGACC_SNK) && + new_state == TYPEC_UNATTACHED) { + dev_info(rpmd->dev, "%s Charger plug out\n", __func__); + /* + * report charger plug-out, + * and disable device connection + */ + cancel_delayed_work_sync(&rpmd->usb_dwork); + rpmd->usb_dr = DR_IDLE; + schedule_delayed_work(&rpmd->usb_dwork, 0); + } else if (old_state == TYPEC_UNATTACHED && + (new_state == TYPEC_ATTACHED_SRC || + new_state == TYPEC_ATTACHED_DEBUG)) { + dev_info(rpmd->dev, + "%s OTG plug in, polarity = %d\n", + __func__, noti->typec_state.polarity); + /* enable host connection */ + cancel_delayed_work_sync(&rpmd->usb_dwork); + rpmd->usb_dr = DR_HOST; + schedule_delayed_work(&rpmd->usb_dwork, 0); + typec_set_data_role(rpmd->typec_port, TYPEC_HOST); + typec_set_pwr_role(rpmd->typec_port, TYPEC_SOURCE); + switch (noti->typec_state.local_rp_level) { + case TYPEC_CC_RP_3_0: + opmode = TYPEC_PWR_MODE_3_0A; + break; + case TYPEC_CC_RP_1_5: + opmode = TYPEC_PWR_MODE_1_5A; + break; + case TYPEC_CC_RP_DFT: + default: + opmode = TYPEC_PWR_MODE_USB; + break; + } + typec_set_pwr_opmode(rpmd->typec_port, opmode); + typec_set_vconn_role(rpmd->typec_port, TYPEC_SOURCE); + } else if ((old_state == TYPEC_ATTACHED_SRC || + old_state == TYPEC_ATTACHED_DEBUG) && + new_state == TYPEC_UNATTACHED) { + dev_info(rpmd->dev, "%s OTG plug out\n", __func__); + /* disable host connection */ + cancel_delayed_work_sync(&rpmd->usb_dwork); + rpmd->usb_dr = DR_IDLE; + schedule_delayed_work(&rpmd->usb_dwork, 0); + } else if (old_state == TYPEC_UNATTACHED && + new_state == TYPEC_ATTACHED_AUDIO) { + dev_info(rpmd->dev, "%s Audio plug in\n", __func__); + /* enable AudioAccessory connection */ + } else if (old_state == TYPEC_ATTACHED_AUDIO && + new_state == TYPEC_UNATTACHED) { + dev_info(rpmd->dev, "%s Audio plug out\n", __func__); + /* disable AudioAccessory connection */ + } + + if (new_state == TYPEC_UNATTACHED) { + val.intval = 0; + smblib_set_prop(rpmd, + POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED, + &val); + smblib_set_prop(rpmd, POWER_SUPPLY_PROP_PD_ACTIVE, + &val); + + typec_unregister_partner(rpmd->partner); + rpmd->partner = NULL; + if (rpmd->typec_caps.prefer_role == TYPEC_SOURCE) { + typec_set_data_role(rpmd->typec_port, + TYPEC_HOST); + typec_set_pwr_role(rpmd->typec_port, + TYPEC_SOURCE); + typec_set_pwr_opmode(rpmd->typec_port, + TYPEC_PWR_MODE_USB); + typec_set_vconn_role(rpmd->typec_port, + TYPEC_SOURCE); + } else { + typec_set_data_role(rpmd->typec_port, + TYPEC_DEVICE); + typec_set_pwr_role(rpmd->typec_port, + TYPEC_SINK); + typec_set_pwr_opmode(rpmd->typec_port, + TYPEC_PWR_MODE_USB); + typec_set_vconn_role(rpmd->typec_port, + TYPEC_SINK); + } + } else if (!rpmd->partner) { + memset(&rpmd->partner_identity, 0, + sizeof(rpmd->partner_identity)); + rpmd->partner_desc.usb_pd = false; + switch (new_state) { + case TYPEC_ATTACHED_AUDIO: + rpmd->partner_desc.accessory = + TYPEC_ACCESSORY_AUDIO; + break; + case TYPEC_ATTACHED_DEBUG: + case TYPEC_ATTACHED_DBGACC_SNK: + case TYPEC_ATTACHED_CUSTOM_SRC: + rpmd->partner_desc.accessory = + TYPEC_ACCESSORY_DEBUG; + break; + default: + rpmd->partner_desc.accessory = + TYPEC_ACCESSORY_NONE; + break; + } + rpmd->partner = typec_register_partner(rpmd->typec_port, + &rpmd->partner_desc); + if (IS_ERR(rpmd->partner)) { + ret = PTR_ERR(rpmd->partner); + dev_notice(rpmd->dev, + "%s typec register partner fail(%d)\n", + __func__, ret); + } + } + + if (new_state == TYPEC_ATTACHED_SNK) { + switch (noti->typec_state.rp_level) { + /* SNK_RP_3P0 */ + case TYPEC_CC_VOLT_SNK_3_0: + break; + /* SNK_RP_1P5 */ + case TYPEC_CC_VOLT_SNK_1_5: + break; + /* SNK_RP_STD */ + case TYPEC_CC_VOLT_SNK_DFT: + default: + break; + } + } else if (new_state == TYPEC_ATTACHED_CUSTOM_SRC || + new_state == TYPEC_ATTACHED_DBGACC_SNK) { + switch (noti->typec_state.rp_level) { + /* DAM_3000 */ + case TYPEC_CC_VOLT_SNK_3_0: + break; + /* DAM_1500 */ + case TYPEC_CC_VOLT_SNK_1_5: + break; + /* DAM_500 */ + case TYPEC_CC_VOLT_SNK_DFT: + default: + break; + } + } else if (new_state == TYPEC_ATTACHED_NORP_SRC) { + /* Both CCs are open */ + } + break; + case TCP_NOTIFY_PR_SWAP: + dev_info(rpmd->dev, "%s power role swap, new role = %d\n", + __func__, noti->swap_state.new_role); + if (noti->swap_state.new_role == PD_ROLE_SINK) { + dev_info(rpmd->dev, "%s swap power role to sink\n", + __func__); + /* + * report charger plug-in without charger type detection + * to not interfering with USB2.0 communication + */ + + /* toggle chg->pd_active to clean up the effect of + * smblib_uusb_removal() */ + val.intval = 10; + smblib_set_prop(rpmd, POWER_SUPPLY_PROP_PD_ACTIVE, + &val); + + typec_set_pwr_role(rpmd->typec_port, TYPEC_SINK); + } else if (noti->swap_state.new_role == PD_ROLE_SOURCE) { + dev_info(rpmd->dev, "%s swap power role to source\n", + __func__); + /* report charger plug-out */ + + typec_set_pwr_role(rpmd->typec_port, TYPEC_SOURCE); + } + break; + case TCP_NOTIFY_DR_SWAP: + dev_info(rpmd->dev, "%s data role swap, new role = %d\n", + __func__, noti->swap_state.new_role); + if (noti->swap_state.new_role == PD_ROLE_UFP) { + dev_info(rpmd->dev, "%s swap data role to device\n", + __func__); + /* + * disable host connection, + * and enable device connection + */ + cancel_delayed_work_sync(&rpmd->usb_dwork); + rpmd->usb_dr = DR_HOST_TO_DEVICE; + schedule_delayed_work(&rpmd->usb_dwork, 0); + typec_set_data_role(rpmd->typec_port, TYPEC_DEVICE); + } else if (noti->swap_state.new_role == PD_ROLE_DFP) { + dev_info(rpmd->dev, "%s swap data role to host\n", + __func__); + /* + * disable device connection, + * and enable host connection + */ + cancel_delayed_work_sync(&rpmd->usb_dwork); + rpmd->usb_dr = DR_DEVICE_TO_HOST; + schedule_delayed_work(&rpmd->usb_dwork, 0); + typec_set_data_role(rpmd->typec_port, TYPEC_HOST); + } + break; + case TCP_NOTIFY_VCONN_SWAP: + dev_info(rpmd->dev, "%s vconn role swap, new role = %d\n", + __func__, noti->swap_state.new_role); + if (noti->swap_state.new_role) { + dev_info(rpmd->dev, "%s swap vconn role to on\n", + __func__); + typec_set_vconn_role(rpmd->typec_port, TYPEC_SOURCE); + } else { + dev_info(rpmd->dev, "%s swap vconn role to off\n", + __func__); + typec_set_vconn_role(rpmd->typec_port, TYPEC_SINK); + } + break; + case TCP_NOTIFY_EXT_DISCHARGE: + dev_info(rpmd->dev, "%s ext discharge = %d\n", + __func__, noti->en_state.en); + /* enable/disable VBUS discharge */ + break; + case TCP_NOTIFY_PD_STATE: + dev_info(rpmd->dev, "%s pd state = %d\n", + __func__, noti->pd_state.connected); + switch (noti->pd_state.connected) { + case PD_CONNECT_NONE: + break; + case PD_CONNECT_HARD_RESET: + break; + case PD_CONNECT_PE_READY_SNK: + case PD_CONNECT_PE_READY_SNK_PD30: + case PD_CONNECT_PE_READY_SNK_APDO: + ret = tcpm_inquire_dpm_flags(rpmd->tcpc); + val.intval = ret & DPM_FLAGS_PARTNER_USB_SUSPEND ? + 1 : 0; + smblib_set_prop(rpmd, + POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED, + &val); + case PD_CONNECT_PE_READY_SRC: + case PD_CONNECT_PE_READY_SRC_PD30: + /* update chg->pd_active */ + val.intval = noti->pd_state.connected == + PD_CONNECT_PE_READY_SNK_APDO ? + POWER_SUPPLY_PD_PPS_ACTIVE : + POWER_SUPPLY_PD_ACTIVE; + smblib_set_prop(rpmd, POWER_SUPPLY_PROP_PD_ACTIVE, + &val); + pd_sink_set_vol_and_cur(rpmd, rpmd->sink_mv_old, + rpmd->sink_ma_old, + TCP_VBUS_CTRL_PD_STANDBY); + + typec_set_pwr_opmode(rpmd->typec_port, + TYPEC_PWR_MODE_PD); + if (!rpmd->partner) + break; + ret = tcpm_inquire_pd_partner_inform(rpmd->tcpc, + partner_vdos); + if (ret != TCPM_SUCCESS) + break; + rpmd->partner_identity.id_header = partner_vdos[0]; + rpmd->partner_identity.cert_stat = partner_vdos[1]; + rpmd->partner_identity.product = partner_vdos[2]; + typec_partner_set_identity(rpmd->partner); + break; + }; + break; + case TCP_NOTIFY_HARD_RESET_STATE: + switch (noti->hreset_state.state) { + case TCP_HRESET_SIGNAL_SEND: + case TCP_HRESET_SIGNAL_RECV: + val.intval = 1; + break; + default: + val.intval = 0; + break; + } + smblib_set_prop(rpmd, POWER_SUPPLY_PROP_PD_IN_HARD_RESET, &val); + break; + default: + break; + }; + return NOTIFY_OK; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) +static int tcpc_typec_try_role(struct typec_port *port, int role) +{ + struct rt_pd_manager_data *rpmd = typec_get_drvdata(port); +#else +static int tcpc_typec_try_role(const struct typec_capability *cap, int role) +{ + struct rt_pd_manager_data *rpmd = + container_of(cap, struct rt_pd_manager_data, typec_caps); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + uint8_t typec_role = TYPEC_ROLE_UNKNOWN; + + dev_info(rpmd->dev, "%s role = %d\n", __func__, role); + + switch (role) { + case TYPEC_NO_PREFERRED_ROLE: + typec_role = TYPEC_ROLE_DRP; + break; + case TYPEC_SINK: + typec_role = TYPEC_ROLE_TRY_SNK; + break; + case TYPEC_SOURCE: + typec_role = TYPEC_ROLE_TRY_SRC; + break; + default: + return 0; + } + + return tcpm_typec_change_role_postpone(rpmd->tcpc, typec_role, true); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) +static int tcpc_typec_dr_set(struct typec_port *port, enum typec_data_role role) +{ + struct rt_pd_manager_data *rpmd = typec_get_drvdata(port); +#else +static int tcpc_typec_dr_set(const struct typec_capability *cap, + enum typec_data_role role) +{ + struct rt_pd_manager_data *rpmd = + container_of(cap, struct rt_pd_manager_data, typec_caps); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + int ret = 0; + uint8_t data_role = tcpm_inquire_pd_data_role(rpmd->tcpc); + bool do_swap = false; + + dev_info(rpmd->dev, "%s role = %d\n", __func__, role); + + if (role == TYPEC_HOST) { + if (data_role == PD_ROLE_UFP) { + do_swap = true; + data_role = PD_ROLE_DFP; + } + } else if (role == TYPEC_DEVICE) { + if (data_role == PD_ROLE_DFP) { + do_swap = true; + data_role = PD_ROLE_UFP; + } + } else { + dev_err(rpmd->dev, "%s invalid role\n", __func__); + return -EINVAL; + } + + if (do_swap) { + ret = tcpm_dpm_pd_data_swap(rpmd->tcpc, data_role, NULL); + if (ret != TCPM_SUCCESS) { + dev_err(rpmd->dev, "%s data role swap fail(%d)\n", + __func__, ret); + return -EPERM; + } + } + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) +static int tcpc_typec_pr_set(struct typec_port *port, enum typec_role role) +{ + struct rt_pd_manager_data *rpmd = typec_get_drvdata(port); +#else +static int tcpc_typec_pr_set(const struct typec_capability *cap, + enum typec_role role) +{ + struct rt_pd_manager_data *rpmd = + container_of(cap, struct rt_pd_manager_data, typec_caps); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + int ret = 0; + uint8_t power_role = tcpm_inquire_pd_power_role(rpmd->tcpc); + bool do_swap = false; + + dev_info(rpmd->dev, "%s role = %d\n", __func__, role); + + if (role == TYPEC_SOURCE) { + if (power_role == PD_ROLE_SINK) { + do_swap = true; + power_role = PD_ROLE_SOURCE; + } + } else if (role == TYPEC_SINK) { + if (power_role == PD_ROLE_SOURCE) { + do_swap = true; + power_role = PD_ROLE_SINK; + } + } else { + dev_err(rpmd->dev, "%s invalid role\n", __func__); + return -EINVAL; + } + + if (do_swap) { + ret = tcpm_dpm_pd_power_swap(rpmd->tcpc, power_role, NULL); + if (ret == TCPM_ERROR_NO_PD_CONNECTED) + ret = tcpm_typec_role_swap(rpmd->tcpc); + if (ret != TCPM_SUCCESS) { + dev_err(rpmd->dev, "%s power role swap fail(%d)\n", + __func__, ret); + return -EPERM; + } + } + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) +static int tcpc_typec_vconn_set(struct typec_port *port, enum typec_role role) +{ + struct rt_pd_manager_data *rpmd = typec_get_drvdata(port); +#else +static int tcpc_typec_vconn_set(const struct typec_capability *cap, + enum typec_role role) +{ + struct rt_pd_manager_data *rpmd = + container_of(cap, struct rt_pd_manager_data, typec_caps); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + int ret = 0; + uint8_t vconn_role = tcpm_inquire_pd_vconn_role(rpmd->tcpc); + bool do_swap = false; + + dev_info(rpmd->dev, "%s role = %d\n", __func__, role); + + if (role == TYPEC_SOURCE) { + if (vconn_role == PD_ROLE_VCONN_OFF) { + do_swap = true; + vconn_role = PD_ROLE_VCONN_ON; + } + } else if (role == TYPEC_SINK) { + if (vconn_role == PD_ROLE_VCONN_ON) { + do_swap = true; + vconn_role = PD_ROLE_VCONN_OFF; + } + } else { + dev_err(rpmd->dev, "%s invalid role\n", __func__); + return -EINVAL; + } + + if (do_swap) { + ret = tcpm_dpm_pd_vconn_swap(rpmd->tcpc, vconn_role, NULL); + if (ret != TCPM_SUCCESS) { + dev_err(rpmd->dev, "%s vconn role swap fail(%d)\n", + __func__, ret); + return -EPERM; + } + } + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) +static int tcpc_typec_port_type_set(struct typec_port *port, + enum typec_port_type type) +{ + struct rt_pd_manager_data *rpmd = typec_get_drvdata(port); + const struct typec_capability *cap = &rpmd->typec_caps; +#else +static int tcpc_typec_port_type_set(const struct typec_capability *cap, + enum typec_port_type type) +{ + struct rt_pd_manager_data *rpmd = + container_of(cap, struct rt_pd_manager_data, typec_caps); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + bool as_sink = tcpc_typec_is_act_as_sink_role(rpmd->tcpc); + uint8_t typec_role = TYPEC_ROLE_UNKNOWN; + + dev_info(rpmd->dev, "%s type = %d, as_sink = %d\n", + __func__, type, as_sink); + + switch (type) { + case TYPEC_PORT_SNK: + if (as_sink) + return 0; + break; + case TYPEC_PORT_SRC: + if (!as_sink) + return 0; + break; + case TYPEC_PORT_DRP: + if (cap->prefer_role == TYPEC_SOURCE) + typec_role = TYPEC_ROLE_TRY_SRC; + else if (cap->prefer_role == TYPEC_SINK) + typec_role = TYPEC_ROLE_TRY_SNK; + else + typec_role = TYPEC_ROLE_DRP; + return tcpm_typec_change_role(rpmd->tcpc, typec_role); + default: + return 0; + } + + return tcpm_typec_role_swap(rpmd->tcpc); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) +const struct typec_operations tcpc_typec_ops = { + .try_role = tcpc_typec_try_role, + .dr_set = tcpc_typec_dr_set, + .pr_set = tcpc_typec_pr_set, + .vconn_set = tcpc_typec_vconn_set, + .port_type_set = tcpc_typec_port_type_set, +}; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + +static int typec_init(struct rt_pd_manager_data *rpmd) +{ + int ret = 0; + uint8_t typec_role = tcpm_inquire_typec_role(rpmd->tcpc); + + rpmd->typec_caps.type = TYPEC_PORT_DRP; + rpmd->typec_caps.data = TYPEC_PORT_DRD; + rpmd->typec_caps.revision = 0x0120; + rpmd->typec_caps.pd_revision = 0x0300; + switch (typec_role) { + case TYPEC_ROLE_SRC: + case TYPEC_ROLE_TRY_SRC: + rpmd->typec_caps.prefer_role = TYPEC_SOURCE; + break; + case TYPEC_ROLE_SNK: + case TYPEC_ROLE_TRY_SNK: + rpmd->typec_caps.prefer_role = TYPEC_SINK; + break; + default: + rpmd->typec_caps.prefer_role = TYPEC_NO_PREFERRED_ROLE; + break; + } +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) + rpmd->typec_caps.driver_data = rpmd; + rpmd->typec_caps.ops = &tcpc_typec_ops; +#else + rpmd->typec_caps.try_role = tcpc_typec_try_role; + rpmd->typec_caps.dr_set = tcpc_typec_dr_set; + rpmd->typec_caps.pr_set = tcpc_typec_pr_set; + rpmd->typec_caps.vconn_set = tcpc_typec_vconn_set; + rpmd->typec_caps.port_type_set = tcpc_typec_port_type_set; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + + rpmd->typec_port = typec_register_port(rpmd->dev, &rpmd->typec_caps); + if (IS_ERR(rpmd->typec_port)) { + ret = PTR_ERR(rpmd->typec_port); + dev_err(rpmd->dev, "%s typec register port fail(%d)\n", + __func__, ret); + goto out; + } + + rpmd->partner_desc.identity = &rpmd->partner_identity; +out: + return ret; +} + +#ifdef CONFIG_TCPC_NOTIFIER_LATE_SYNC +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_RECV_BAT_ABSENT_NOTIFY +static int fg_bat_notifier_call(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct pd_port *pd_port = container_of(nb, struct pd_port, fg_bat_nb); + struct tcpc_device *tcpc = pd_port->tcpc; + + switch (event) { + case EVENT_BATTERY_PLUG_OUT: + dev_info(&tcpc->dev, "%s: fg battery absent\n", __func__); + schedule_work(&pd_port->fg_bat_work); + break; + default: + break; + } + + return NOTIFY_OK; +} +#endif /* CONFIG_RECV_BAT_ABSENT_NOTIFY */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + +static int __tcpc_class_complete_work(struct device *dev, void *data) +{ + struct tcpc_device *tcpc = dev_get_drvdata(dev); +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_RECV_BAT_ABSENT_NOTIFY + struct notifier_block *fg_bat_nb = &tcpc->pd_port.fg_bat_nb; + int ret = 0; +#endif /* CONFIG_RECV_BAT_ABSENT_NOTIFY */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + + if (tcpc != NULL) { + pr_info("%s = %s\n", __func__, dev_name(dev)); +#if 1 + tcpc_device_irq_enable(tcpc); +#else + schedule_delayed_work(&tcpc->init_work, + msecs_to_jiffies(1000)); +#endif + +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_RECV_BAT_ABSENT_NOTIFY + fg_bat_nb->notifier_call = fg_bat_notifier_call; + ret = register_battery_notifier(fg_bat_nb); + if (ret < 0) { + pr_notice("%s: register bat notifier fail\n", __func__); + return -EINVAL; + } +#endif /* CONFIG_RECV_BAT_ABSENT_NOTIFY */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + } + return 0; +} + +static int tcpc_class_complete_init(void) +{ + if (!IS_ERR(tcpc_class)) { + class_for_each_device(tcpc_class, NULL, NULL, + __tcpc_class_complete_work); + } + return 0; +} +#endif /* CONFIG_TCPC_NOTIFIER_LATE_SYNC */ + +static int rt_pd_manager_probe(struct platform_device *pdev) +{ + int ret = 0; + static int probe_cnt = 0; + struct rt_pd_manager_data *rpmd = NULL; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) + int i = 0; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + + dev_info(&pdev->dev, "%s (%s) probe_cnt = %d\n", + __func__, RT_PD_MANAGER_VERSION, ++probe_cnt); + + rpmd = devm_kzalloc(&pdev->dev, sizeof(*rpmd), GFP_KERNEL); + if (!rpmd) + return -ENOMEM; + + rpmd->dev = &pdev->dev; + + ret = extcon_init(rpmd); + if (ret) { + dev_err(rpmd->dev, "%s init extcon fail(%d)\n", __func__, ret); + ret = -EPROBE_DEFER; + if (probe_cnt >= PROBE_CNT_MAX) + goto out; + else + goto err_init_extcon; + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) + rpmd->iio_channels = devm_kcalloc(rpmd->dev, POWER_SUPPLY_IIO_PROP_MAX, + sizeof(rpmd->iio_channels[0]), + GFP_KERNEL); + if (!rpmd->iio_channels) { + dev_err(rpmd->dev, "%s kcalloc fail\n", __func__); + ret = -EPROBE_DEFER; + if (probe_cnt >= PROBE_CNT_MAX) + goto out; + else + goto err_get_iio_chan; + } + for (i = 0; i < POWER_SUPPLY_IIO_PROP_MAX; i++) { + rpmd->iio_channels[i] = + devm_iio_channel_get(rpmd->dev, iio_channel_map[i]); + if (IS_ERR(rpmd->iio_channels[i])) { + ret = PTR_ERR(rpmd->iio_channels[i]); + dev_err(rpmd->dev, "%s get iio chan %s fail(%d)\n", + __func__, iio_channel_map[i], ret); + ret = -EPROBE_DEFER; + if (probe_cnt >= PROBE_CNT_MAX) + goto out; + else + goto err_get_iio_chan; + } + } +#else + rpmd->usb_psy = power_supply_get_by_name("usb"); + if (!rpmd->usb_psy) { + dev_err(rpmd->dev, "%s get usb psy fail\n", __func__); + ret = -EPROBE_DEFER; + if (probe_cnt >= PROBE_CNT_MAX) + goto out; + else + goto err_get_usb_psy; + } +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ + + rpmd->tcpc = tcpc_dev_get_by_name("type_c_port0"); + if (!rpmd->tcpc) { + dev_err(rpmd->dev, "%s get tcpc dev fail\n", __func__); + ret = -EPROBE_DEFER; + if (probe_cnt >= PROBE_CNT_MAX) + goto out; + else + goto err_get_tcpc_dev; + } + + INIT_DELAYED_WORK(&rpmd->usb_dwork, usb_dwork_handler); + rpmd->usb_dr = DR_IDLE; + rpmd->usb_type_polling_cnt = 0; + rpmd->sink_mv_old = -1; + rpmd->sink_ma_old = -1; + + ret = typec_init(rpmd); + if (ret < 0) { + dev_err(rpmd->dev, "%s init typec fail(%d)\n", __func__, ret); + ret = -EPROBE_DEFER; + if (probe_cnt >= PROBE_CNT_MAX) + goto out; + else + goto err_init_typec; + } + + rpmd->pd_nb.notifier_call = pd_tcp_notifier_call; + ret = register_tcp_dev_notifier(rpmd->tcpc, &rpmd->pd_nb, + TCP_NOTIFY_TYPE_ALL); + if (ret < 0) { + dev_err(rpmd->dev, "%s register tcpc notifier fail(%d)\n", + __func__, ret); + ret = -EPROBE_DEFER; + if (probe_cnt >= PROBE_CNT_MAX) + goto out; + else + goto err_reg_tcpc_notifier; + } +#ifdef CONFIG_TCPC_NOTIFIER_LATE_SYNC + tcpc_class_complete_init(); +#endif /* CONFIG_TCPC_NOTIFIER_LATE_SYNC */ +out: + platform_set_drvdata(pdev, rpmd); + dev_info(rpmd->dev, "%s %s!!\n", __func__, ret == -EPROBE_DEFER ? + "Over probe cnt max" : "OK"); + return 0; + +err_reg_tcpc_notifier: + typec_unregister_port(rpmd->typec_port); +err_init_typec: +err_get_tcpc_dev: +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) +err_get_iio_chan: +#else + power_supply_put(rpmd->usb_psy); +err_get_usb_psy: +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) */ +err_init_extcon: + return ret; +} + +static int rt_pd_manager_remove(struct platform_device *pdev) +{ + int ret = 0; + struct rt_pd_manager_data *rpmd = platform_get_drvdata(pdev); + + if (!rpmd) + return -EINVAL; + + ret = unregister_tcp_dev_notifier(rpmd->tcpc, &rpmd->pd_nb, + TCP_NOTIFY_TYPE_ALL); + if (ret < 0) + dev_err(rpmd->dev, "%s unregister tcpc notifier fail(%d)\n", + __func__, ret); + typec_unregister_port(rpmd->typec_port); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) + if (rpmd->usb_psy) + power_supply_put(rpmd->usb_psy); +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) */ + + return ret; +} + +static const struct of_device_id rt_pd_manager_of_match[] = { + { .compatible = "richtek,rt-pd-manager" }, + { } +}; +MODULE_DEVICE_TABLE(of, rt_pd_manager_of_match); + +static struct platform_driver rt_pd_manager_driver = { + .driver = { + .name = "rt-pd-manager", + .of_match_table = of_match_ptr(rt_pd_manager_of_match), + }, + .probe = rt_pd_manager_probe, + .remove = rt_pd_manager_remove, +}; +module_platform_driver(rt_pd_manager_driver); + +MODULE_AUTHOR("Jeff Chang"); +MODULE_DESCRIPTION("Richtek pd manager driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(RT_PD_MANAGER_VERSION); + +/* + * Release Note + * 0.0.8 + * (1) Add support for msm-5.4 + * + * 0.0.7 + * (1) Set properties of usb_psy + * + * 0.0.6 + * (1) Register typec_port + * + * 0.0.5 + * (1) Control USB mode in delayed work + * (2) Remove param_lock because pd_tcp_notifier_call() is single-entry + * + * 0.0.4 + * (1) Limit probe count + * + * 0.0.3 + * (1) Add extcon for controlling USB mode + * + * 0.0.2 + * (1) Initialize old_state and new_state + * + * 0.0.1 + * (1) Add all possible notification events + */ diff --git a/drivers/usb/typec/mmi_tcpc/tcpc_rt1711h.c b/drivers/usb/typec/mmi_tcpc/tcpc_rt1711h.c new file mode 100644 index 000000000000..4efdee9d7204 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpc_rt1711h.c @@ -0,0 +1,1812 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Richtek RT1711H Type-C Port Control Driver + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inc/pd_dbg_info.h" +#include "inc/tcpci.h" +#include "inc/rt1711h.h" + +#ifdef CONFIG_RT_REGMAP +#include "inc/rt-regmap.h" +#endif /* CONFIG_RT_REGMAP */ + +#if 1 /* #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0))*/ +#include +#endif /* #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)) */ + +/* #define DEBUG_GPIO 66 */ + +#define RT1711H_DRV_VERSION "2.0.5_G" + +#define RT1711H_IRQ_WAKE_TIME (500) /* ms */ + +struct rt1711_chip { + struct i2c_client *client; + struct device *dev; +#ifdef CONFIG_RT_REGMAP + struct rt_regmap_device *m_dev; +#endif /* CONFIG_RT_REGMAP */ + struct semaphore io_lock; + struct semaphore suspend_lock; + struct tcpc_desc *tcpc_desc; + struct tcpc_device *tcpc; + struct kthread_worker irq_worker; + struct kthread_work irq_work; + struct task_struct *irq_worker_task; + struct wakeup_source *irq_wake_lock; +#ifdef CONFIG_TCPC_RT1711H + atomic_t poll_count; + struct delayed_work poll_work; +#endif /* CONFIG_TCPC_RT1711H */ + int irq_gpio; + int irq; + int chip_id; +}; + +#ifdef CONFIG_RT_REGMAP +RT_REG_DECL(TCPC_V10_REG_VID, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_PID, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_DID, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_TYPEC_REV, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_PD_REV, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_PDIF_REV, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_ALERT, 2, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_ALERT_MASK, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_POWER_STATUS_MASK, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_FAULT_STATUS_MASK, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_TCPC_CTRL, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_ROLE_CTRL, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_FAULT_CTRL, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_POWER_CTRL, 1, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_CC_STATUS, 1, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_POWER_STATUS, 1, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_FAULT_STATUS, 1, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_COMMAND, 1, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_MSG_HDR_INFO, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_RX_DETECT, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_RX_BYTE_CNT, 1, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_RX_BUF_FRAME_TYPE, 1, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_RX_HDR, 2, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_RX_DATA, 28, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_TRANSMIT, 1, RT_VOLATILE, {}); +RT_REG_DECL(TCPC_V10_REG_TX_BYTE_CNT, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_TX_HDR, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(TCPC_V10_REG_TX_DATA, 28, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_CONFIG_GPIO0, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_PHY_CTRL1, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_CLK_CTRL2, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_CLK_CTRL3, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_PRL_FSM_RESET, 1, RT_VOLATILE, {}); +RT_REG_DECL(RT1711H_REG_BMC_CTRL, 1, RT_VOLATILE, {}); +RT_REG_DECL(RT1711H_REG_BMCIO_RXDZSEL, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_VCONN_CLIMITEN, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_RT_STATUS, 1, RT_VOLATILE, {}); +RT_REG_DECL(RT1711H_REG_RT_INT, 1, RT_VOLATILE, {}); +RT_REG_DECL(RT1711H_REG_RT_MASK, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_IDLE_CTRL, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_INTRST_CTRL, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_WATCHDOG_CTRL, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_I2CRST_CTRL, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_SWRESET, 1, RT_VOLATILE, {}); +RT_REG_DECL(RT1711H_REG_TTCPC_FILTER, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_DRP_TOGGLE_CYCLE, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_DRP_DUTY_CTRL, 2, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_BMCIO_RXDZEN, 1, RT_NORMAL_WR_ONCE, {}); +RT_REG_DECL(RT1711H_REG_UNLOCK_PW_2, 2, RT_VOLATILE, {}); +RT_REG_DECL(RT1711H_REG_EFUSE5, 1, RT_VOLATILE, {}); + +static const rt_register_map_t rt1711_chip_regmap[] = { + RT_REG(TCPC_V10_REG_VID), + RT_REG(TCPC_V10_REG_PID), + RT_REG(TCPC_V10_REG_DID), + RT_REG(TCPC_V10_REG_TYPEC_REV), + RT_REG(TCPC_V10_REG_PD_REV), + RT_REG(TCPC_V10_REG_PDIF_REV), + RT_REG(TCPC_V10_REG_ALERT), + RT_REG(TCPC_V10_REG_ALERT_MASK), + RT_REG(TCPC_V10_REG_POWER_STATUS_MASK), + RT_REG(TCPC_V10_REG_FAULT_STATUS_MASK), + RT_REG(TCPC_V10_REG_TCPC_CTRL), + RT_REG(TCPC_V10_REG_ROLE_CTRL), + RT_REG(TCPC_V10_REG_FAULT_CTRL), + RT_REG(TCPC_V10_REG_POWER_CTRL), + RT_REG(TCPC_V10_REG_CC_STATUS), + RT_REG(TCPC_V10_REG_POWER_STATUS), + RT_REG(TCPC_V10_REG_FAULT_STATUS), + RT_REG(TCPC_V10_REG_COMMAND), + RT_REG(TCPC_V10_REG_MSG_HDR_INFO), + RT_REG(TCPC_V10_REG_RX_DETECT), + RT_REG(TCPC_V10_REG_RX_BYTE_CNT), + RT_REG(TCPC_V10_REG_RX_BUF_FRAME_TYPE), + RT_REG(TCPC_V10_REG_RX_HDR), + RT_REG(TCPC_V10_REG_RX_DATA), + RT_REG(TCPC_V10_REG_TRANSMIT), + RT_REG(TCPC_V10_REG_TX_BYTE_CNT), + RT_REG(TCPC_V10_REG_TX_HDR), + RT_REG(TCPC_V10_REG_TX_DATA), + RT_REG(RT1711H_REG_CONFIG_GPIO0), + RT_REG(RT1711H_REG_PHY_CTRL1), + RT_REG(RT1711H_REG_CLK_CTRL2), + RT_REG(RT1711H_REG_CLK_CTRL3), + RT_REG(RT1711H_REG_PRL_FSM_RESET), + RT_REG(RT1711H_REG_BMC_CTRL), + RT_REG(RT1711H_REG_BMCIO_RXDZSEL), + RT_REG(RT1711H_REG_VCONN_CLIMITEN), + RT_REG(RT1711H_REG_RT_STATUS), + RT_REG(RT1711H_REG_RT_INT), + RT_REG(RT1711H_REG_RT_MASK), + RT_REG(RT1711H_REG_IDLE_CTRL), + RT_REG(RT1711H_REG_INTRST_CTRL), + RT_REG(RT1711H_REG_WATCHDOG_CTRL), + RT_REG(RT1711H_REG_I2CRST_CTRL), + RT_REG(RT1711H_REG_SWRESET), + RT_REG(RT1711H_REG_TTCPC_FILTER), + RT_REG(RT1711H_REG_DRP_TOGGLE_CYCLE), + RT_REG(RT1711H_REG_DRP_DUTY_CTRL), + RT_REG(RT1711H_REG_BMCIO_RXDZEN), + RT_REG(RT1711H_REG_UNLOCK_PW_2), + RT_REG(RT1711H_REG_EFUSE5), +}; +#define RT1711_CHIP_REGMAP_SIZE ARRAY_SIZE(rt1711_chip_regmap) + +#endif /* CONFIG_RT_REGMAP */ + +static int rt1711_read_device(void *client, u32 reg, int len, void *dst) +{ + struct i2c_client *i2c = client; + int ret = 0, count = 5; + u64 t1 = 0, t2 = 0; + + while (1) { + t1 = local_clock(); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, len, dst); + t2 = local_clock(); + RT1711_INFO("%s del = %lluus, reg = %02X, len = %d\n", + __func__, (t2 - t1) / NSEC_PER_USEC, reg, len); + if (ret < 0 && count > 1) + count--; + else + break; + udelay(100); + } + return ret; +} + +static int rt1711_write_device(void *client, u32 reg, int len, const void *src) +{ + struct i2c_client *i2c = client; + int ret = 0, count = 5; + u64 t1 = 0, t2 = 0; + + while (1) { + t1 = local_clock(); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, len, src); + t2 = local_clock(); + RT1711_INFO("%s del = %lluus, reg = %02X, len = %d\n", + __func__, (t2 - t1) / NSEC_PER_USEC, reg, len); + if (ret < 0 && count > 1) + count--; + else + break; + udelay(100); + } + return ret; +} + +static int rt1711_reg_read(struct i2c_client *i2c, u8 reg) +{ + struct rt1711_chip *chip = i2c_get_clientdata(i2c); + u8 val = 0; + int ret = 0; + +#ifdef CONFIG_RT_REGMAP + ret = rt_regmap_block_read(chip->m_dev, reg, 1, &val); +#else + ret = rt1711_read_device(chip->client, reg, 1, &val); +#endif /* CONFIG_RT_REGMAP */ + if (ret < 0) { + dev_err(chip->dev, "rt1711 reg read fail\n"); + return ret; + } + return val; +} + +static int rt1711_reg_write(struct i2c_client *i2c, u8 reg, const u8 data) +{ + struct rt1711_chip *chip = i2c_get_clientdata(i2c); + int ret = 0; + +#ifdef CONFIG_RT_REGMAP + ret = rt_regmap_block_write(chip->m_dev, reg, 1, &data); +#else + ret = rt1711_write_device(chip->client, reg, 1, &data); +#endif /* CONFIG_RT_REGMAP */ + if (ret < 0) + dev_err(chip->dev, "rt1711 reg write fail\n"); + return ret; +} + +static int rt1711_block_read(struct i2c_client *i2c, + u8 reg, int len, void *dst) +{ + struct rt1711_chip *chip = i2c_get_clientdata(i2c); + int ret = 0; +#ifdef CONFIG_RT_REGMAP + ret = rt_regmap_block_read(chip->m_dev, reg, len, dst); +#else + ret = rt1711_read_device(chip->client, reg, len, dst); +#endif /* #ifdef CONFIG_RT_REGMAP */ + if (ret < 0) + dev_err(chip->dev, "rt1711 block read fail\n"); + return ret; +} + +static int rt1711_block_write(struct i2c_client *i2c, + u8 reg, int len, const void *src) +{ + struct rt1711_chip *chip = i2c_get_clientdata(i2c); + int ret = 0; +#ifdef CONFIG_RT_REGMAP + ret = rt_regmap_block_write(chip->m_dev, reg, len, src); +#else + ret = rt1711_write_device(chip->client, reg, len, src); +#endif /* #ifdef CONFIG_RT_REGMAP */ + if (ret < 0) + dev_err(chip->dev, "rt1711 block write fail\n"); + return ret; +} + +static int32_t rt1711_write_word(struct i2c_client *client, + uint8_t reg_addr, uint16_t data) +{ + int ret; + + /* don't need swap */ + ret = rt1711_block_write(client, reg_addr, 2, (uint8_t *)&data); + return ret; +} + +static int32_t rt1711_read_word(struct i2c_client *client, + uint8_t reg_addr, uint16_t *data) +{ + int ret; + + /* don't need swap */ + ret = rt1711_block_read(client, reg_addr, 2, (uint8_t *)data); + return ret; +} + +static inline int rt1711_i2c_write8( + struct tcpc_device *tcpc, u8 reg, const u8 data) +{ + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + + return rt1711_reg_write(chip->client, reg, data); +} + +static inline int rt1711_i2c_write16( + struct tcpc_device *tcpc, u8 reg, const u16 data) +{ + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + + return rt1711_write_word(chip->client, reg, data); +} + +static inline int rt1711_i2c_read8(struct tcpc_device *tcpc, u8 reg) +{ + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + + return rt1711_reg_read(chip->client, reg); +} + +static inline int rt1711_i2c_read16( + struct tcpc_device *tcpc, u8 reg) +{ + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + u16 data; + int ret; + + ret = rt1711_read_word(chip->client, reg, &data); + if (ret < 0) + return ret; + return data; +} + +#ifdef CONFIG_RT_REGMAP +static struct rt_regmap_fops rt1711_regmap_fops = { + .read_device = rt1711_read_device, + .write_device = rt1711_write_device, +}; +#endif /* CONFIG_RT_REGMAP */ + +static int rt1711_regmap_init(struct rt1711_chip *chip) +{ +#ifdef CONFIG_RT_REGMAP + struct rt_regmap_properties *props; + char name[32]; + int len; + + props = devm_kzalloc(chip->dev, sizeof(*props), GFP_KERNEL); + if (!props) + return -ENOMEM; + + props->register_num = RT1711_CHIP_REGMAP_SIZE; + props->rm = rt1711_chip_regmap; + + props->rt_regmap_mode = RT_MULTI_BYTE | + RT_IO_PASS_THROUGH | RT_DBG_SPECIAL; + snprintf(name, sizeof(name), "rt1711-%02x", chip->client->addr); + + len = strlen(name); + props->name = kzalloc(len+1, GFP_KERNEL); + props->aliases = kzalloc(len+1, GFP_KERNEL); + + if ((!props->name) || (!props->aliases)) + return -ENOMEM; + + strlcpy((char *)props->name, name, len+1); + strlcpy((char *)props->aliases, name, len+1); + props->io_log_en = 0; + + chip->m_dev = rt_regmap_device_register(props, + &rt1711_regmap_fops, chip->dev, chip->client, chip); + if (!chip->m_dev) { + dev_err(chip->dev, "rt1711 chip rt_regmap register fail\n"); + return -EINVAL; + } +#endif + return 0; +} + +static int rt1711_regmap_deinit(struct rt1711_chip *chip) +{ +#ifdef CONFIG_RT_REGMAP + rt_regmap_device_unregister(chip->m_dev); +#endif + return 0; +} + +static inline int rt1711_software_reset(struct tcpc_device *tcpc) +{ + int ret = rt1711_i2c_write8(tcpc, RT1711H_REG_SWRESET, 1); +#ifdef CONFIG_RT_REGMAP + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); +#endif /* CONFIG_RT_REGMAP */ + + if (ret < 0) + return ret; +#ifdef CONFIG_RT_REGMAP + rt_regmap_cache_reload(chip->m_dev); +#endif /* CONFIG_RT_REGMAP */ + usleep_range(1000, 2000); + return 0; +} + +static inline int rt1711_command(struct tcpc_device *tcpc, uint8_t cmd) +{ + return rt1711_i2c_write8(tcpc, TCPC_V10_REG_COMMAND, cmd); +} + +static int rt1711_init_vbus_cal(struct tcpc_device *tcpc) +{ + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + const u8 val_en_test_mode[] = {0x86, 0x62}; + const u8 val_dis_test_mode[] = {0x00, 0x00}; + int ret = 0; + u8 data = 0; + s8 cal = 0; + + ret = rt1711_block_write(chip->client, RT1711H_REG_UNLOCK_PW_2, + ARRAY_SIZE(val_en_test_mode), val_en_test_mode); + if (ret < 0) + dev_notice(chip->dev, "%s en test mode fail(%d)\n", + __func__, ret); + + ret = rt1711_reg_read(chip->client, RT1711H_REG_EFUSE5); + if (ret < 0) + goto out; + + data = ret; + data = (data & RT1711H_REG_M_VBUS_CAL) >> RT1711H_REG_S_VBUS_CAL; + cal = (data & BIT(2)) ? (data | GENMASK(7, 3)) : data; + cal -= 2; + if (cal < RT1711H_REG_MIN_VBUS_CAL) + cal = RT1711H_REG_MIN_VBUS_CAL; + data = (cal << RT1711H_REG_S_VBUS_CAL) | (ret & GENMASK(4, 0)); + + ret = rt1711_reg_write(chip->client, RT1711H_REG_EFUSE5, data); +out: + ret = rt1711_block_write(chip->client, RT1711H_REG_UNLOCK_PW_2, + ARRAY_SIZE(val_dis_test_mode), val_dis_test_mode); + if (ret < 0) + dev_notice(chip->dev, "%s dis test mode fail(%d)\n", + __func__, ret); + + return ret; +} + +static int rt1711_init_alert_mask(struct tcpc_device *tcpc) +{ + uint16_t mask; + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + + mask = TCPC_V10_REG_ALERT_CC_STATUS | TCPC_V10_REG_ALERT_POWER_STATUS; + +#ifdef CONFIG_USB_POWER_DELIVERY + /* Need to handle RX overflow */ + mask |= TCPC_V10_REG_ALERT_TX_SUCCESS | TCPC_V10_REG_ALERT_TX_DISCARDED + | TCPC_V10_REG_ALERT_TX_FAILED + | TCPC_V10_REG_ALERT_RX_HARD_RST + | TCPC_V10_REG_ALERT_RX_STATUS + | TCPC_V10_REG_RX_OVERFLOW; +#endif + + mask |= TCPC_REG_ALERT_FAULT; + + return rt1711_write_word(chip->client, TCPC_V10_REG_ALERT_MASK, mask); +} + +static int rt1711_init_power_status_mask(struct tcpc_device *tcpc) +{ + const uint8_t mask = TCPC_V10_REG_POWER_STATUS_VBUS_PRES; + + return rt1711_i2c_write8(tcpc, + TCPC_V10_REG_POWER_STATUS_MASK, mask); +} + +static int rt1711_init_fault_mask(struct tcpc_device *tcpc) +{ + const uint8_t mask = + TCPC_V10_REG_FAULT_STATUS_VCONN_OV | + TCPC_V10_REG_FAULT_STATUS_VCONN_OC; + + return rt1711_i2c_write8(tcpc, + TCPC_V10_REG_FAULT_STATUS_MASK, mask); +} + +static int rt1711_init_rt_mask(struct tcpc_device *tcpc) +{ + uint8_t rt_mask = 0; +#ifdef CONFIG_TCPC_WATCHDOG_EN + rt_mask |= RT1711H_REG_M_WATCHDOG; +#endif /* CONFIG_TCPC_WATCHDOG_EN */ +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + rt_mask |= RT1711H_REG_M_VBUS_80; +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + +#ifdef CONFIG_TYPEC_CAP_RA_DETACH + if (tcpc->tcpc_flags & TCPC_FLAGS_CHECK_RA_DETACHE) + rt_mask |= RT1711H_REG_M_RA_DETACH; +#endif /* CONFIG_TYPEC_CAP_RA_DETACH */ + +#ifdef CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG + if (tcpc->tcpc_flags & TCPC_FLAGS_LPM_WAKEUP_WATCHDOG) + rt_mask |= RT1711H_REG_M_WAKEUP; +#endif /* CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG */ + + return rt1711_i2c_write8(tcpc, RT1711H_REG_RT_MASK, rt_mask); +} + +#ifdef CONFIG_TCPC_RT1711H +static inline void rt1711_poll_ctrl(struct rt1711_chip *chip) +{ + cancel_delayed_work_sync(&chip->poll_work); + + if (atomic_read(&chip->poll_count) == 0) { + atomic_inc(&chip->poll_count); + cpu_idle_poll_ctrl(true); + } + + schedule_delayed_work( + &chip->poll_work, msecs_to_jiffies(40)); +} +#endif /* CONFIG_TCPC_RT1711H */ + +static void rt1711_irq_work_handler(struct kthread_work *work) +{ + struct rt1711_chip *chip = + container_of(work, struct rt1711_chip, irq_work); + int regval = 0; + int gpio_val; + +#ifdef CONFIG_TCPC_RT1711H + rt1711_poll_ctrl(chip); +#endif /* CONFIG_TCPC_RT1711H */ + /* make sure I2C bus had resumed */ + down(&chip->suspend_lock); + tcpci_lock_typec(chip->tcpc); + +#ifdef DEBUG_GPIO + gpio_set_value(DEBUG_GPIO, 1); +#endif + + do { + regval = tcpci_alert(chip->tcpc); + if (regval) + break; + gpio_val = gpio_get_value(chip->irq_gpio); + } while (gpio_val == 0); + + tcpci_unlock_typec(chip->tcpc); + up(&chip->suspend_lock); + +#ifdef DEBUG_GPIO + gpio_set_value(DEBUG_GPIO, 1); +#endif +} + +#ifdef CONFIG_TCPC_RT1711H +static void rt1711_poll_work(struct work_struct *work) +{ + struct rt1711_chip *chip = container_of( + work, struct rt1711_chip, poll_work.work); + + if (atomic_dec_and_test(&chip->poll_count)) + cpu_idle_poll_ctrl(false); +} +#endif /* CONFIG_TCPC_RT1711H */ + +static irqreturn_t rt1711_intr_handler(int irq, void *data) +{ + struct rt1711_chip *chip = data; + + __pm_wakeup_event(chip->irq_wake_lock, RT1711H_IRQ_WAKE_TIME); + +#ifdef DEBUG_GPIO + gpio_set_value(DEBUG_GPIO, 0); +#endif + kthread_queue_work(&chip->irq_worker, &chip->irq_work); + return IRQ_HANDLED; +} + +static int rt1711_init_alert(struct tcpc_device *tcpc) +{ + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; + int ret; + char *name; + int len; + + /* Clear Alert Mask & Status */ + rt1711_write_word(chip->client, TCPC_V10_REG_ALERT_MASK, 0); + rt1711_write_word(chip->client, TCPC_V10_REG_ALERT, 0xffff); + + len = strlen(chip->tcpc_desc->name); + name = devm_kzalloc(chip->dev, len+5, GFP_KERNEL); + if (!name) + return -ENOMEM; + + snprintf(name, PAGE_SIZE, "%s-IRQ", chip->tcpc_desc->name); + + pr_info("%s name = %s, gpio = %d\n", __func__, + chip->tcpc_desc->name, chip->irq_gpio); + + ret = devm_gpio_request(chip->dev, chip->irq_gpio, name); +#ifdef DEBUG_GPIO + gpio_request(DEBUG_GPIO, "debug_latency_pin"); + gpio_direction_output(DEBUG_GPIO, 1); +#endif + if (ret < 0) { + pr_err("Error: failed to request GPIO%d (ret = %d)\n", + chip->irq_gpio, ret); + goto init_alert_err; + } + + ret = gpio_direction_input(chip->irq_gpio); + if (ret < 0) { + pr_err("Error: failed to set GPIO%d as input pin(ret = %d)\n", + chip->irq_gpio, ret); + goto init_alert_err; + } + + chip->irq = gpio_to_irq(chip->irq_gpio); + if (chip->irq <= 0) { + pr_err("%s gpio to irq fail, chip->irq(%d)\n", + __func__, chip->irq); + goto init_alert_err; + } + + pr_info("%s : IRQ number = %d\n", __func__, chip->irq); + + kthread_init_worker(&chip->irq_worker); + chip->irq_worker_task = kthread_run(kthread_worker_fn, + &chip->irq_worker, "%s", chip->tcpc_desc->name); + if (IS_ERR(chip->irq_worker_task)) { + pr_err("Error: Could not create tcpc task\n"); + goto init_alert_err; + } + + sched_setscheduler(chip->irq_worker_task, SCHED_FIFO, ¶m); + kthread_init_work(&chip->irq_work, rt1711_irq_work_handler); + + pr_info("IRQF_NO_THREAD Test\n"); + ret = request_irq(chip->irq, rt1711_intr_handler, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, name, chip); + if (ret < 0) { + pr_err("Error: failed to request irq%d (gpio = %d, ret = %d)\n", + chip->irq, chip->irq_gpio, ret); + goto init_alert_err; + } + + enable_irq_wake(chip->irq); + return 0; +init_alert_err: + return -EINVAL; +} + +int rt1711_alert_status_clear(struct tcpc_device *tcpc, uint32_t mask) +{ + int ret; + uint16_t mask_t1; + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + uint8_t mask_t2; +#endif + + /* Write 1 clear */ + mask_t1 = (uint16_t) mask; + if (mask_t1) { + ret = rt1711_i2c_write16(tcpc, TCPC_V10_REG_ALERT, mask_t1); + if (ret < 0) + return ret; + } + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + mask_t2 = mask >> 16; + if (mask_t2) { + ret = rt1711_i2c_write8(tcpc, RT1711H_REG_RT_INT, mask_t2); + if (ret < 0) + return ret; + } +#endif + + return 0; +} + +static int rt1711h_set_clock_gating(struct tcpc_device *tcpc, bool en) +{ + int ret = 0; + +#ifdef CONFIG_TCPC_CLOCK_GATING + int i = 0; + uint8_t clk2 = RT1711H_REG_CLK_DIV_600K_EN + | RT1711H_REG_CLK_DIV_300K_EN | RT1711H_REG_CLK_CK_300K_EN; + uint8_t clk3 = RT1711H_REG_CLK_DIV_2P4M_EN; + + if (!en) { + clk2 |= + RT1711H_REG_CLK_BCLK2_EN | RT1711H_REG_CLK_BCLK_EN; + clk3 |= + RT1711H_REG_CLK_CK_24M_EN | RT1711H_REG_CLK_PCLK_EN; + } + + if (en) { + for (i = 0; i < 2; i++) + ret = rt1711_alert_status_clear(tcpc, + TCPC_REG_ALERT_RX_ALL_MASK); + } + + if (ret == 0) + ret = rt1711_i2c_write8(tcpc, RT1711H_REG_CLK_CTRL2, clk2); + if (ret == 0) + ret = rt1711_i2c_write8(tcpc, RT1711H_REG_CLK_CTRL3, clk3); +#endif /* CONFIG_TCPC_CLOCK_GATING */ + + return ret; +} + +static inline int rt1711h_init_cc_params( + struct tcpc_device *tcpc, uint8_t cc_res) +{ + int rv = 0; + +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_USB_PD_SNK_DFT_NO_GOOD_CRC + uint8_t en, sel; + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + + if (cc_res == TYPEC_CC_VOLT_SNK_DFT) { /* 0.55 */ + en = 0; + sel = 0x81; + } else if (chip->chip_id >= RT1715_DID_D) { /* 0.35 & 0.75 */ + en = 1; + sel = 0x81; + } else { /* 0.4 & 0.7 */ + en = 1; + sel = 0x80; + } + + rv = rt1711_i2c_write8(tcpc, RT1711H_REG_BMCIO_RXDZEN, en); + if (rv == 0) + rv = rt1711_i2c_write8(tcpc, RT1711H_REG_BMCIO_RXDZSEL, sel); +#endif /* CONFIG_USB_PD_SNK_DFT_NO_GOOD_CRC */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + + return rv; +} + +static int rt1711_tcpc_init(struct tcpc_device *tcpc, bool sw_reset) +{ + int ret; + bool retry_discard_old = false; + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + + RT1711_INFO("\n"); + + if (sw_reset) { + ret = rt1711_software_reset(tcpc); + if (ret < 0) + return ret; + } + +#ifdef CONFIG_TCPC_I2CRST_EN + rt1711_i2c_write8(tcpc, + RT1711H_REG_I2CRST_CTRL, + RT1711H_REG_I2CRST_SET(true, 0x0f)); +#endif /* CONFIG_TCPC_I2CRST_EN */ + + /* UFP Both RD setting */ + /* DRP = 0, RpVal = 0 (Default), Rd, Rd */ + rt1711_i2c_write8(tcpc, TCPC_V10_REG_ROLE_CTRL, + TCPC_V10_REG_ROLE_CTRL_RES_SET(0, 0, CC_RD, CC_RD)); + + if (chip->chip_id == RT1711H_DID_A) { + rt1711_i2c_write8(tcpc, TCPC_V10_REG_FAULT_CTRL, + TCPC_V10_REG_FAULT_CTRL_DIS_VCONN_OV); + } + + /* + * CC Detect Debounce : 26.7*val us + * Transition window count : spec 12~20us, based on 2.4MHz + * DRP Toggle Cycle : 51.2 + 6.4*val ms + * DRP Duyt Ctrl : dcSRC: /1024 + */ + + rt1711_i2c_write8(tcpc, RT1711H_REG_TTCPC_FILTER, 10); + rt1711_i2c_write8(tcpc, RT1711H_REG_DRP_TOGGLE_CYCLE, 4); + rt1711_i2c_write16(tcpc, + RT1711H_REG_DRP_DUTY_CTRL, TCPC_NORMAL_RP_DUTY); + + /* Vconn OC */ + rt1711_i2c_write8(tcpc, RT1711H_REG_VCONN_CLIMITEN, 1); + + /* RX/TX Clock Gating (Auto Mode)*/ + if (!sw_reset) + rt1711h_set_clock_gating(tcpc, true); + + if (!(tcpc->tcpc_flags & TCPC_FLAGS_RETRY_CRC_DISCARD)) + retry_discard_old = true; + + rt1711_i2c_write8(tcpc, RT1711H_REG_CONFIG_GPIO0, 0x80); + + /* For BIST, Change Transition Toggle Counter (Noise) from 3 to 7 */ + rt1711_i2c_write8(tcpc, RT1711H_REG_PHY_CTRL1, + RT1711H_REG_PHY_CTRL1_SET(retry_discard_old, 7, 0, 1)); + + tcpci_alert_status_clear(tcpc, 0xffffffff); + + rt1711_init_vbus_cal(tcpc); + rt1711_init_power_status_mask(tcpc); + rt1711_init_alert_mask(tcpc); + rt1711_init_fault_mask(tcpc); + rt1711_init_rt_mask(tcpc); + + /* CK_300K from 320K, SHIPPING off, AUTOIDLE enable, TIMEOUT = 6.4ms */ + rt1711_i2c_write8(tcpc, RT1711H_REG_IDLE_CTRL, + RT1711H_REG_IDLE_SET(0, 1, 1, 0)); + mdelay(1); + + return 0; +} + +static inline int rt1711_fault_status_vconn_ov(struct tcpc_device *tcpc) +{ + int ret; + + ret = rt1711_i2c_read8(tcpc, RT1711H_REG_BMC_CTRL); + if (ret < 0) + return ret; + + ret &= ~RT1711H_REG_DISCHARGE_EN; + return rt1711_i2c_write8(tcpc, RT1711H_REG_BMC_CTRL, ret); +} + +int rt1711_fault_status_clear(struct tcpc_device *tcpc, uint8_t status) +{ + int ret; + + if (status & TCPC_V10_REG_FAULT_STATUS_VCONN_OV) + ret = rt1711_fault_status_vconn_ov(tcpc); + + rt1711_i2c_write8(tcpc, TCPC_V10_REG_FAULT_STATUS, status); + return 0; +} + +int rt1711_get_alert_mask(struct tcpc_device *tcpc, uint32_t *mask) +{ + int ret; +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + uint8_t v2; +#endif + + ret = rt1711_i2c_read16(tcpc, TCPC_V10_REG_ALERT_MASK); + if (ret < 0) + return ret; + + *mask = (uint16_t) ret; + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + ret = rt1711_i2c_read8(tcpc, RT1711H_REG_RT_MASK); + if (ret < 0) + return ret; + + v2 = (uint8_t) ret; + *mask |= v2 << 16; +#endif + + return 0; +} + +int rt1711_get_alert_status(struct tcpc_device *tcpc, uint32_t *alert) +{ + int ret; +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + uint8_t v2; +#endif + + ret = rt1711_i2c_read16(tcpc, TCPC_V10_REG_ALERT); + if (ret < 0) + return ret; + + *alert = (uint16_t) ret; + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + ret = rt1711_i2c_read8(tcpc, RT1711H_REG_RT_INT); + if (ret < 0) + return ret; + + v2 = (uint8_t) ret; + *alert |= v2 << 16; +#endif + + return 0; +} + +static int rt1711_get_power_status( + struct tcpc_device *tcpc, uint16_t *pwr_status) +{ + int ret; + + ret = rt1711_i2c_read8(tcpc, TCPC_V10_REG_POWER_STATUS); + if (ret < 0) + return ret; + + *pwr_status = 0; + + if (ret & TCPC_V10_REG_POWER_STATUS_VBUS_PRES) + *pwr_status |= TCPC_REG_POWER_STATUS_VBUS_PRES; + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + ret = rt1711_i2c_read8(tcpc, RT1711H_REG_RT_STATUS); + if (ret < 0) + return ret; + + if (ret & RT1711H_REG_VBUS_80) + *pwr_status |= TCPC_REG_POWER_STATUS_EXT_VSAFE0V; +#endif + return 0; +} + +int rt1711_get_fault_status(struct tcpc_device *tcpc, uint8_t *status) +{ + int ret; + + ret = rt1711_i2c_read8(tcpc, TCPC_V10_REG_FAULT_STATUS); + if (ret < 0) + return ret; + *status = (uint8_t) ret; + return 0; +} + +static int rt1711_get_cc(struct tcpc_device *tcpc, int *cc1, int *cc2) +{ + int status, role_ctrl, cc_role; + bool act_as_sink, act_as_drp; + + status = rt1711_i2c_read8(tcpc, TCPC_V10_REG_CC_STATUS); + if (status < 0) + return status; + + role_ctrl = rt1711_i2c_read8(tcpc, TCPC_V10_REG_ROLE_CTRL); + if (role_ctrl < 0) + return role_ctrl; + + if (status & TCPC_V10_REG_CC_STATUS_DRP_TOGGLING) { + *cc1 = TYPEC_CC_DRP_TOGGLING; + *cc2 = TYPEC_CC_DRP_TOGGLING; + return 0; + } + + *cc1 = TCPC_V10_REG_CC_STATUS_CC1(status); + *cc2 = TCPC_V10_REG_CC_STATUS_CC2(status); + + act_as_drp = TCPC_V10_REG_ROLE_CTRL_DRP & role_ctrl; + + if (act_as_drp) { + act_as_sink = TCPC_V10_REG_CC_STATUS_DRP_RESULT(status); + } else { + cc_role = TCPC_V10_REG_CC_STATUS_CC1(role_ctrl); + if (cc_role == TYPEC_CC_RP) + act_as_sink = false; + else + act_as_sink = true; + } + + /* + * If status is not open, then OR in termination to convert to + * enum tcpc_cc_voltage_status. + */ + + if (*cc1 != TYPEC_CC_VOLT_OPEN) + *cc1 |= (act_as_sink << 2); + + if (*cc2 != TYPEC_CC_VOLT_OPEN) + *cc2 |= (act_as_sink << 2); + + rt1711h_init_cc_params(tcpc, + (uint8_t)tcpc->typec_polarity ? *cc2 : *cc1); + + return 0; +} + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC +static int rt1711_enable_vsafe0v_detect( + struct tcpc_device *tcpc, bool enable) +{ + int ret = rt1711_i2c_read8(tcpc, RT1711H_REG_RT_MASK); + + if (ret < 0) + return ret; + + if (enable) + ret |= RT1711H_REG_M_VBUS_80; + else + ret &= ~RT1711H_REG_M_VBUS_80; + + return rt1711_i2c_write8(tcpc, RT1711H_REG_RT_MASK, (uint8_t) ret); +} +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + +static int rt1711_set_cc(struct tcpc_device *tcpc, int pull) +{ + int ret; + uint8_t data; + int rp_lvl = TYPEC_CC_PULL_GET_RP_LVL(pull), pull1, pull2; + + RT1711_INFO("\n"); + pull = TYPEC_CC_PULL_GET_RES(pull); + if (pull == TYPEC_CC_DRP) { + data = TCPC_V10_REG_ROLE_CTRL_RES_SET( + 1, rp_lvl, TYPEC_CC_RD, TYPEC_CC_RD); + + ret = rt1711_i2c_write8( + tcpc, TCPC_V10_REG_ROLE_CTRL, data); + + if (ret == 0) { +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + rt1711_enable_vsafe0v_detect(tcpc, false); +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + ret = rt1711_command(tcpc, TCPM_CMD_LOOK_CONNECTION); + } + } else { +#ifdef CONFIG_USB_POWER_DELIVERY + if (pull == TYPEC_CC_RD && tcpc->pd_wait_pr_swap_complete) + rt1711h_init_cc_params(tcpc, TYPEC_CC_VOLT_SNK_DFT); +#endif /* CONFIG_USB_POWER_DELIVERY */ + + pull1 = pull2 = pull; + + if ((pull == TYPEC_CC_RP_DFT || pull == TYPEC_CC_RP_1_5 || + pull == TYPEC_CC_RP_3_0) && + tcpc->typec_is_attached_src) { + if (tcpc->typec_polarity) + pull1 = TYPEC_CC_OPEN; + else + pull2 = TYPEC_CC_OPEN; + } + data = TCPC_V10_REG_ROLE_CTRL_RES_SET(0, rp_lvl, pull1, pull2); + ret = rt1711_i2c_write8(tcpc, TCPC_V10_REG_ROLE_CTRL, data); + } + + return 0; +} + +static int rt1711_set_polarity(struct tcpc_device *tcpc, int polarity) +{ + int data; + + data = rt1711h_init_cc_params(tcpc, + tcpc->typec_remote_cc[polarity]); + if (data) + return data; + + data = rt1711_i2c_read8(tcpc, TCPC_V10_REG_TCPC_CTRL); + if (data < 0) + return data; + + data &= ~TCPC_V10_REG_TCPC_CTRL_PLUG_ORIENT; + data |= polarity ? TCPC_V10_REG_TCPC_CTRL_PLUG_ORIENT : 0; + + return rt1711_i2c_write8(tcpc, TCPC_V10_REG_TCPC_CTRL, data); +} + +static int rt1711_set_low_rp_duty(struct tcpc_device *tcpc, bool low_rp) +{ + uint16_t duty = low_rp ? TCPC_LOW_RP_DUTY : TCPC_NORMAL_RP_DUTY; + + return rt1711_i2c_write16(tcpc, RT1711H_REG_DRP_DUTY_CTRL, duty); +} + +static int rt1711_set_vconn(struct tcpc_device *tcpc, int enable) +{ + int rv; + int data; + + data = rt1711_i2c_read8(tcpc, TCPC_V10_REG_POWER_CTRL); + if (data < 0) + return data; + + data &= ~TCPC_V10_REG_POWER_CTRL_VCONN; + data |= enable ? TCPC_V10_REG_POWER_CTRL_VCONN : 0; + + rv = rt1711_i2c_write8(tcpc, TCPC_V10_REG_POWER_CTRL, data); + if (rv < 0) + return rv; + + return rt1711_i2c_write8(tcpc, RT1711H_REG_IDLE_CTRL, + RT1711H_REG_IDLE_SET(0, 1, enable ? 0 : 1, 0)); +} + +#ifdef CONFIG_TCPC_LOW_POWER_MODE +static int rt1711_is_low_power_mode(struct tcpc_device *tcpc) +{ + int rv = rt1711_i2c_read8(tcpc, RT1711H_REG_BMC_CTRL); + + if (rv < 0) + return rv; + + return (rv & RT1711H_REG_BMCIO_LPEN) != 0; +} + +static int rt1711_set_low_power_mode( + struct tcpc_device *tcpc, bool en, int pull) +{ + int ret = 0; + uint8_t data; + + ret = rt1711_i2c_write8(tcpc, RT1711H_REG_IDLE_CTRL, + RT1711H_REG_IDLE_SET(0, 1, en ? 0 : 1, 0)); + if (ret < 0) + return ret; +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + rt1711_enable_vsafe0v_detect(tcpc, !en); +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + if (en) { + data = RT1711H_REG_BMCIO_LPEN; + + if (pull & TYPEC_CC_RP) + data |= RT1711H_REG_BMCIO_LPRPRD; + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + data |= RT1711H_REG_BMCIO_BG_EN | RT1711H_REG_VBUS_DET_EN; +#endif + } else { + data = RT1711H_REG_BMCIO_BG_EN | + RT1711H_REG_VBUS_DET_EN | RT1711H_REG_BMCIO_OSC_EN; + } + + return rt1711_i2c_write8(tcpc, RT1711H_REG_BMC_CTRL, data); +} +#endif /* CONFIG_TCPC_LOW_POWER_MODE */ + +#ifdef CONFIG_TCPC_WATCHDOG_EN +int rt1711h_set_watchdog(struct tcpc_device *tcpc, bool en) +{ + uint8_t data = RT1711H_REG_WATCHDOG_CTRL_SET(en, 7); + + return rt1711_i2c_write8(tcpc, + RT1711H_REG_WATCHDOG_CTRL, data); +} +#endif /* CONFIG_TCPC_WATCHDOG_EN */ + +#ifdef CONFIG_TCPC_INTRST_EN +int rt1711h_set_intrst(struct tcpc_device *tcpc, bool en) +{ + return rt1711_i2c_write8(tcpc, + RT1711H_REG_INTRST_CTRL, RT1711H_REG_INTRST_SET(en, 3)); +} +#endif /* CONFIG_TCPC_INTRST_EN */ + +static int rt1711_tcpc_deinit(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_RT_REGMAP + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); +#endif /* CONFIG_RT_REGMAP */ + +#ifdef CONFIG_TCPC_SHUTDOWN_CC_DETACH + rt1711_set_cc(tcpc, TYPEC_CC_DRP); + rt1711_set_cc(tcpc, TYPEC_CC_OPEN); + + rt1711_i2c_write8(tcpc, + RT1711H_REG_I2CRST_CTRL, + RT1711H_REG_I2CRST_SET(true, 4)); + + rt1711_i2c_write8(tcpc, + RT1711H_REG_INTRST_CTRL, + RT1711H_REG_INTRST_SET(true, 0)); +#else + rt1711_i2c_write8(tcpc, RT1711H_REG_SWRESET, 1); +#endif /* CONFIG_TCPC_SHUTDOWN_CC_DETACH */ +#ifdef CONFIG_RT_REGMAP + rt_regmap_cache_reload(chip->m_dev); +#endif /* CONFIG_RT_REGMAP */ + + return 0; +} + +#ifdef CONFIG_USB_POWER_DELIVERY +static int rt1711_set_msg_header( + struct tcpc_device *tcpc, uint8_t power_role, uint8_t data_role) +{ + uint8_t msg_hdr = TCPC_V10_REG_MSG_HDR_INFO_SET( + data_role, power_role); + + return rt1711_i2c_write8( + tcpc, TCPC_V10_REG_MSG_HDR_INFO, msg_hdr); +} + +static int rt1711_protocol_reset(struct tcpc_device *tcpc) +{ + rt1711_i2c_write8(tcpc, RT1711H_REG_PRL_FSM_RESET, 0); + mdelay(1); + rt1711_i2c_write8(tcpc, RT1711H_REG_PRL_FSM_RESET, 1); + return 0; +} + +static int rt1711_set_rx_enable(struct tcpc_device *tcpc, uint8_t enable) +{ + int ret = 0; + + if (enable) + ret = rt1711h_set_clock_gating(tcpc, false); + + if (ret == 0) + ret = rt1711_i2c_write8(tcpc, TCPC_V10_REG_RX_DETECT, enable); + + if ((ret == 0) && (!enable)) { + rt1711_protocol_reset(tcpc); + ret = rt1711h_set_clock_gating(tcpc, true); + } + + return ret; +} + +static int rt1711_get_message(struct tcpc_device *tcpc, uint32_t *payload, + uint16_t *msg_head, enum tcpm_transmit_type *frame_type) +{ + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + int rv; + uint8_t type, cnt = 0; + uint8_t buf[4]; + const uint16_t alert_rx = + TCPC_V10_REG_ALERT_RX_STATUS|TCPC_V10_REG_RX_OVERFLOW; + + rv = rt1711_block_read(chip->client, + TCPC_V10_REG_RX_BYTE_CNT, 4, buf); + cnt = buf[0]; + type = buf[1]; + *msg_head = *(uint16_t *)&buf[2]; + + /* TCPC 1.0 ==> no need to subtract the size of msg_head */ + if (rv >= 0 && cnt > 3) { + cnt -= 3; /* MSG_HDR */ + rv = rt1711_block_read(chip->client, TCPC_V10_REG_RX_DATA, cnt, + (uint8_t *) payload); + } + + *frame_type = (enum tcpm_transmit_type) type; + + /* Read complete, clear RX status alert bit */ + tcpci_alert_status_clear(tcpc, alert_rx); + + /*mdelay(1); */ + return rv; +} + +static int rt1711_set_bist_carrier_mode( + struct tcpc_device *tcpc, uint8_t pattern) +{ + /* Don't support this function */ + return 0; +} + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD +static int rt1711_retransmit(struct tcpc_device *tcpc) +{ + return rt1711_i2c_write8(tcpc, TCPC_V10_REG_TRANSMIT, + TCPC_V10_REG_TRANSMIT_SET( + tcpc->pd_retry_count, TCPC_TX_SOP)); +} +#endif + +#pragma pack(push, 1) +struct tcpc_transmit_packet { + uint8_t cnt; + uint16_t msg_header; + uint8_t data[sizeof(uint32_t)*7]; +}; +#pragma pack(pop) + +static int rt1711_transmit(struct tcpc_device *tcpc, + enum tcpm_transmit_type type, uint16_t header, const uint32_t *data) +{ + struct rt1711_chip *chip = tcpc_get_dev_data(tcpc); + int rv; + int data_cnt; + struct tcpc_transmit_packet packet; + + if (type < TCPC_TX_HARD_RESET) { + data_cnt = sizeof(uint32_t) * PD_HEADER_CNT(header); + + packet.cnt = data_cnt + sizeof(uint16_t); + packet.msg_header = header; + + if (data_cnt > 0) + memcpy(packet.data, (uint8_t *) data, data_cnt); + + rv = rt1711_block_write(chip->client, + TCPC_V10_REG_TX_BYTE_CNT, + packet.cnt+1, (uint8_t *) &packet); + if (rv < 0) + return rv; + } + + rv = rt1711_i2c_write8(tcpc, TCPC_V10_REG_TRANSMIT, + TCPC_V10_REG_TRANSMIT_SET( + tcpc->pd_retry_count, type)); + return rv; +} + +static int rt1711_set_bist_test_mode(struct tcpc_device *tcpc, bool en) +{ + int data; + + data = rt1711_i2c_read8(tcpc, TCPC_V10_REG_TCPC_CTRL); + if (data < 0) + return data; + + data &= ~TCPC_V10_REG_TCPC_CTRL_BIST_TEST_MODE; + data |= en ? TCPC_V10_REG_TCPC_CTRL_BIST_TEST_MODE : 0; + + return rt1711_i2c_write8(tcpc, TCPC_V10_REG_TCPC_CTRL, data); +} +#endif /* CONFIG_USB_POWER_DELIVERY */ + +static struct tcpc_ops rt1711_tcpc_ops = { + .init = rt1711_tcpc_init, + .alert_status_clear = rt1711_alert_status_clear, + .fault_status_clear = rt1711_fault_status_clear, + .get_alert_mask = rt1711_get_alert_mask, + .get_alert_status = rt1711_get_alert_status, + .get_power_status = rt1711_get_power_status, + .get_fault_status = rt1711_get_fault_status, + .get_cc = rt1711_get_cc, + .set_cc = rt1711_set_cc, + .set_polarity = rt1711_set_polarity, + .set_low_rp_duty = rt1711_set_low_rp_duty, + .set_vconn = rt1711_set_vconn, + .deinit = rt1711_tcpc_deinit, + +#ifdef CONFIG_TCPC_LOW_POWER_MODE + .is_low_power_mode = rt1711_is_low_power_mode, + .set_low_power_mode = rt1711_set_low_power_mode, +#endif /* CONFIG_TCPC_LOW_POWER_MODE */ + +#ifdef CONFIG_TCPC_WATCHDOG_EN + .set_watchdog = rt1711h_set_watchdog, +#endif /* CONFIG_TCPC_WATCHDOG_EN */ + +#ifdef CONFIG_TCPC_INTRST_EN + .set_intrst = rt1711h_set_intrst, +#endif /* CONFIG_TCPC_INTRST_EN */ + +#ifdef CONFIG_USB_POWER_DELIVERY + .set_msg_header = rt1711_set_msg_header, + .set_rx_enable = rt1711_set_rx_enable, + .protocol_reset = rt1711_protocol_reset, + .get_message = rt1711_get_message, + .transmit = rt1711_transmit, + .set_bist_test_mode = rt1711_set_bist_test_mode, + .set_bist_carrier_mode = rt1711_set_bist_carrier_mode, +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + .retransmit = rt1711_retransmit, +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ +}; + +static int rt_parse_dt(struct rt1711_chip *chip, struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret = 0; + + pr_info("%s\n", __func__); + +#if (!defined(CONFIG_MTK_GPIO) || defined(CONFIG_MTK_GPIOLIB_STAND)) + ret = of_get_named_gpio(np, "rt1711pd,intr_gpio", 0); + if (ret < 0) { + pr_err("%s no intr_gpio info\n", __func__); + return ret; + } + chip->irq_gpio = ret; +#else + ret = of_property_read_u32(np, + "rt1711pd,intr_gpio_num", &chip->irq_gpio); + if (ret < 0) + pr_err("%s no intr_gpio info\n", __func__); +#endif + return ret < 0 ? ret : 0; +} + +/* + * In some platform pr_info may spend too much time on printing debug message. + * So we use this function to test the printk performance. + * If your platform cannot not pass this check function, please config + * PD_DBG_INFO, this will provide the threaded debug message for you. + */ +#if TCPC_ENABLE_ANYMSG +static void check_printk_performance(void) +{ + int i; + u64 t1, t2; + u32 nsrem; + +#ifdef CONFIG_PD_DBG_INFO + for (i = 0; i < 10; i++) { + t1 = local_clock(); + pd_dbg_info("%d\n", i); + t2 = local_clock(); + t2 -= t1; + nsrem = do_div(t2, 1000000000); + pd_dbg_info("pd_dbg_info : t2-t1 = %lu\n", + (unsigned long)nsrem / 1000); + } + for (i = 0; i < 10; i++) { + t1 = local_clock(); + pr_info("%d\n", i); + t2 = local_clock(); + t2 -= t1; + nsrem = do_div(t2, 1000000000); + pr_info("pr_info : t2-t1 = %lu\n", + (unsigned long)nsrem / 1000); + } +#else + for (i = 0; i < 10; i++) { + t1 = local_clock(); + pr_info("%d\n", i); + t2 = local_clock(); + t2 -= t1; + nsrem = do_div(t2, 1000000000); + pr_info("t2-t1 = %lu\n", + (unsigned long)nsrem / 1000); + PD_BUG_ON(nsrem > 100*1000); + } +#endif /* CONFIG_PD_DBG_INFO */ +} +#endif /* TCPC_ENABLE_ANYMSG */ + +static int rt1711_tcpcdev_init(struct rt1711_chip *chip, struct device *dev) +{ + struct tcpc_desc *desc; + struct device_node *np = dev->of_node; + u32 val, len; + const char *name = "default"; + + dev_info(dev, "%s\n", __func__); + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + if (of_property_read_u32(np, "rt-tcpc,role_def", &val) >= 0) { + if (val >= TYPEC_ROLE_NR) + desc->role_def = TYPEC_ROLE_DRP; + else + desc->role_def = val; + } else { + dev_info(dev, "use default Role DRP\n"); + desc->role_def = TYPEC_ROLE_DRP; + } + + if (of_property_read_u32( + np, "rt-tcpc,notifier_supply_num", &val) >= 0) { + if (val < 0) + desc->notifier_supply_num = 0; + else + desc->notifier_supply_num = val; + } else + desc->notifier_supply_num = 0; + + if (of_property_read_u32(np, "rt-tcpc,rp_level", &val) >= 0) { + switch (val) { + case 0: /* RP Default */ + desc->rp_lvl = TYPEC_CC_RP_DFT; + break; + case 1: /* RP 1.5V */ + desc->rp_lvl = TYPEC_CC_RP_1_5; + break; + case 2: /* RP 3.0V */ + desc->rp_lvl = TYPEC_CC_RP_3_0; + break; + default: + break; + } + } + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + if (of_property_read_u32(np, "rt-tcpc,vconn_supply", &val) >= 0) { + if (val >= TCPC_VCONN_SUPPLY_NR) + desc->vconn_supply = TCPC_VCONN_SUPPLY_ALWAYS; + else + desc->vconn_supply = val; + } else { + dev_info(dev, "use default VconnSupply\n"); + desc->vconn_supply = TCPC_VCONN_SUPPLY_ALWAYS; + } +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + + if (of_property_read_string(np, "rt-tcpc,name", + (char const **)&name) < 0) { + dev_info(dev, "use default name\n"); + } + + len = strlen(name); + desc->name = kzalloc(len+1, GFP_KERNEL); + if (!desc->name) + return -ENOMEM; + + strlcpy((char *)desc->name, name, len+1); + + chip->tcpc_desc = desc; + + chip->tcpc = tcpc_device_register(dev, + desc, &rt1711_tcpc_ops, chip); + if (IS_ERR(chip->tcpc)) + return -EINVAL; + + chip->tcpc->tcpc_flags = TCPC_FLAGS_LPM_WAKEUP_WATCHDOG | + TCPC_FLAGS_VCONN_SAFE5V_ONLY; + + if (chip->chip_id > RT1711H_DID_B) + chip->tcpc->tcpc_flags |= TCPC_FLAGS_CHECK_RA_DETACHE; + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + if (chip->chip_id > RT1715_DID_D) + chip->tcpc->tcpc_flags |= TCPC_FLAGS_RETRY_CRC_DISCARD; +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + +#ifdef CONFIG_USB_PD_REV30 + if (chip->chip_id >= RT1715_DID_D) + chip->tcpc->tcpc_flags |= TCPC_FLAGS_PD_REV30; + + if (chip->tcpc->tcpc_flags & TCPC_FLAGS_PD_REV30) + dev_info(dev, "PD_REV30\n"); + else + dev_info(dev, "PD_REV20\n"); +#endif /* CONFIG_USB_PD_REV30 */ + chip->tcpc->tcpc_flags |= TCPC_FLAGS_ALERT_V10; + + return 0; +} + +#define RICHTEK_1711_VID 0x29cf +#define RICHTEK_1711_PID 0x1711 + +static inline int rt1711h_check_revision(struct i2c_client *client) +{ + u16 vid, pid, did; + int ret; + u8 data = 1; + + ret = rt1711_read_device(client, TCPC_V10_REG_VID, 2, &vid); + if (ret < 0) { + dev_err(&client->dev, "read chip ID fail(%d)\n", ret); + return -EIO; + } + + if (vid != RICHTEK_1711_VID) { + pr_info("%s failed, VID=0x%04x\n", __func__, vid); + return -ENODEV; + } + + ret = rt1711_read_device(client, TCPC_V10_REG_PID, 2, &pid); + if (ret < 0) { + dev_err(&client->dev, "read product ID fail(%d)\n", ret); + return -EIO; + } + + if (pid != RICHTEK_1711_PID) { + pr_info("%s failed, PID=0x%04x\n", __func__, pid); + return -ENODEV; + } + + ret = rt1711_write_device(client, RT1711H_REG_SWRESET, 1, &data); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + + ret = rt1711_read_device(client, TCPC_V10_REG_DID, 2, &did); + if (ret < 0) { + dev_err(&client->dev, "read device ID fail(%d)\n", ret); + return -EIO; + } + + return did; +} + +static int rt1711_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct rt1711_chip *chip; + int ret = 0, chip_id; + bool use_dt = client->dev.of_node; + + pr_info("%s (%s)\n", __func__, RT1711H_DRV_VERSION); + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BYTE_DATA)) + pr_info("I2C functionality : OK...\n"); + else + pr_info("I2C functionality check : failuare...\n"); + + chip_id = rt1711h_check_revision(client); + if (chip_id < 0) + return chip_id; + +#if TCPC_ENABLE_ANYMSG + check_printk_performance(); +#endif /* TCPC_ENABLE_ANYMSG */ + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + if (use_dt) { + ret = rt_parse_dt(chip, &client->dev); + if (ret < 0) + return ret; + } else { + dev_err(&client->dev, "no dts node\n"); + return -ENODEV; + } + chip->dev = &client->dev; + chip->client = client; + sema_init(&chip->io_lock, 1); + sema_init(&chip->suspend_lock, 1); + i2c_set_clientdata(client, chip); +#ifdef CONFIG_TCPC_RT1711H + INIT_DELAYED_WORK(&chip->poll_work, rt1711_poll_work); +#endif /* CONFIG_TCPC_RT1711H */ + chip->irq_wake_lock = + wakeup_source_register(chip->dev, "rt1711h_irq_wake_lock"); + + chip->chip_id = chip_id; + pr_info("rt1711h_chipID = 0x%0x\n", chip_id); + + ret = rt1711_regmap_init(chip); + if (ret < 0) { + dev_err(chip->dev, "rt1711 regmap init fail\n"); + goto err_regmap_init; + } + + ret = rt1711_tcpcdev_init(chip, &client->dev); + if (ret < 0) { + dev_err(&client->dev, "rt1711 tcpc dev init fail\n"); + goto err_tcpc_reg; + } + + ret = rt1711_init_alert(chip->tcpc); + if (ret < 0) { + pr_err("rt1711 init alert fail\n"); + goto err_irq_init; + } + + tcpc_schedule_init_work(chip->tcpc); + pr_info("%s probe OK!\n", __func__); + return 0; + +err_irq_init: + tcpc_device_unregister(chip->dev, chip->tcpc); +err_tcpc_reg: + rt1711_regmap_deinit(chip); +err_regmap_init: + wakeup_source_unregister(chip->irq_wake_lock); + return ret; +} + +static int rt1711_i2c_remove(struct i2c_client *client) +{ + struct rt1711_chip *chip = i2c_get_clientdata(client); + + if (chip) { +#ifdef CONFIG_TCPC_RT1711H + cancel_delayed_work_sync(&chip->poll_work); +#endif /* CONFIG_TCPC_RT1711H */ + tcpc_device_unregister(chip->dev, chip->tcpc); + rt1711_regmap_deinit(chip); + } + + return 0; +} + +#ifdef CONFIG_PM +static int rt1711_i2c_suspend(struct device *dev) +{ + struct rt1711_chip *chip; + struct i2c_client *client = to_i2c_client(dev); + + if (client) { + chip = i2c_get_clientdata(client); + if (chip) + down(&chip->suspend_lock); + } + + return 0; +} + +static int rt1711_i2c_resume(struct device *dev) +{ + struct rt1711_chip *chip; + struct i2c_client *client = to_i2c_client(dev); + + if (client) { + chip = i2c_get_clientdata(client); + if (chip) + up(&chip->suspend_lock); + } + + return 0; +} + +static void rt1711_shutdown(struct i2c_client *client) +{ + struct rt1711_chip *chip = i2c_get_clientdata(client); + + /* Please reset IC here */ + if (chip != NULL) { + if (chip->irq) + disable_irq(chip->irq); + tcpm_shutdown(chip->tcpc); + } else { + i2c_smbus_write_byte_data( + client, RT1711H_REG_SWRESET, 0x01); + } +} + +#ifdef CONFIG_PM_RUNTIME +static int rt1711_pm_suspend_runtime(struct device *device) +{ + dev_dbg(device, "pm_runtime: suspending...\n"); + return 0; +} + +static int rt1711_pm_resume_runtime(struct device *device) +{ + dev_dbg(device, "pm_runtime: resuming...\n"); + return 0; +} +#endif /* #ifdef CONFIG_PM_RUNTIME */ + +static const struct dev_pm_ops rt1711_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS( + rt1711_i2c_suspend, + rt1711_i2c_resume) +#ifdef CONFIG_PM_RUNTIME + SET_RUNTIME_PM_OPS( + rt1711_pm_suspend_runtime, + rt1711_pm_resume_runtime, + NULL + ) +#endif /* #ifdef CONFIG_PM_RUNTIME */ +}; +#define RT1711_PM_OPS (&rt1711_pm_ops) +#else +#define RT1711_PM_OPS (NULL) +#endif /* CONFIG_PM */ + +static const struct i2c_device_id rt1711_id_table[] = { + {"rt1711h", 0}, + {"rt1715", 0}, + {"rt1716", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, rt1711_id_table); + +static const struct of_device_id rt_match_table[] = { + {.compatible = "richtek,rt1711h",}, + {.compatible = "richtek,rt1715",}, + {.compatible = "richtek,rt1716",}, + {}, +}; + +static struct i2c_driver rt1711_driver = { + .driver = { + .name = "usb_type_c", + .owner = THIS_MODULE, + .of_match_table = rt_match_table, + .pm = RT1711_PM_OPS, + }, + .probe = rt1711_i2c_probe, + .remove = rt1711_i2c_remove, + .shutdown = rt1711_shutdown, + .id_table = rt1711_id_table, +}; + +module_i2c_driver(rt1711_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jeff Chang "); +MODULE_DESCRIPTION("RT1711 TCPC Driver"); +MODULE_VERSION(RT1711H_DRV_VERSION); + +/**** Release Note **** + * 2.0.5_G + * (1) Utilize rt-regmap to reduce I2C accesses + * (2) Decrease VBUS present threshold (VBUS_CAL) by 60mV (2LSBs) + * + * 2.0.4_G + * (1) Mask vSafe0V IRQ before entering low power mode + * (2) Disable auto idle mode before entering low power mode + * (3) Reset Protocol FSM and clear RX alerts twice before clock gating + * + * 2.0.3_G + * (1) Single Rp as Attatched.SRC for Ellisys TD.4.9.4 + * + * 2.0.2_G + * (1) Replace wake_lock with wakeup_source + * (2) Move down the shipping off + * (3) Add support for NoRp.SRC + * (4) Reg0x71[7] = 1'b1 to workaround unstable VDD Iq in low power mode + * (5) Add get_alert_mask of tcpc_ops + * + * 2.0.1_G + * First released PD3.0 Driver + */ diff --git a/drivers/usb/typec/mmi_tcpc/tcpci.c b/drivers/usb/typec/mmi_tcpc/tcpci.c new file mode 100644 index 000000000000..bb868ecaa294 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpci.c @@ -0,0 +1,989 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/tcpci.h" +#include +#include + +#define TCPC_NOTIFY_OVERTIME (20) /* ms */ + +#ifdef CONFIG_TCPC_NOTIFICATION_NON_BLOCKING +struct tcp_notify_work { + struct work_struct work; + struct tcpc_device *tcpc; + struct tcp_notify tcp_noti; + uint8_t type; + uint8_t state; +}; + +static void tcp_notify_func(struct work_struct *work) +{ + struct tcp_notify_work *tn_work = + container_of(work, struct tcp_notify_work, work); + struct tcpc_device *tcpc = tn_work->tcpc; + struct tcp_notify *tcp_noti = &tn_work->tcp_noti; + uint8_t type = tn_work->type; + uint8_t state = tn_work->state; +#ifdef CONFIG_PD_BEGUG_ON + struct timeval begin, end; + int timeval = 0; + + do_gettimeofday(&begin); + srcu_notifier_call_chain(&tcpc->evt_nh[type], state, tcp_noti); + do_gettimeofday(&end); + timeval = (timeval_to_ns(end) - timeval_to_ns(begin))/1000/1000; + PD_BUG_ON(timeval > TCPC_NOTIFY_OVERTIME); +#else + srcu_notifier_call_chain(&tcpc->evt_nh[type], state, tcp_noti); +#endif + + kfree(tn_work); +} + +static int tcpc_check_notify_time(struct tcpc_device *tcpc, + struct tcp_notify *tcp_noti, uint8_t type, uint8_t state) +{ + struct tcp_notify_work *tn_work; + + tn_work = kzalloc(sizeof(*tn_work), GFP_KERNEL); + if (!tn_work) + return -ENOMEM; + + INIT_WORK(&tn_work->work, tcp_notify_func); + tn_work->tcpc = tcpc; + tn_work->tcp_noti = *tcp_noti; + tn_work->type = type; + tn_work->state = state; + + return queue_work(tcpc->evt_wq, &tn_work->work) ? 0 : -EAGAIN; +} +#else +static int tcpc_check_notify_time(struct tcpc_device *tcpc, + struct tcp_notify *tcp_noti, uint8_t type, uint8_t state) +{ + int ret; +#ifdef CONFIG_PD_BEGUG_ON + struct timeval begin, end; + int timeval = 0; + + do_gettimeofday(&begin); + ret = srcu_notifier_call_chain(&tcpc->evt_nh[type], state, tcp_noti); + do_gettimeofday(&end); + timeval = (timeval_to_ns(end) - timeval_to_ns(begin))/1000/1000; + PD_BUG_ON(timeval > TCPC_NOTIFY_OVERTIME); +#else + ret = srcu_notifier_call_chain(&tcpc->evt_nh[type], state, tcp_noti); +#endif + return ret; +} +#endif /* CONFIG_TCPC_NOTIFICATION_BLOCKING */ + +int tcpci_check_vbus_valid_from_ic(struct tcpc_device *tcpc) +{ + uint16_t power_status; + int vbus_level = tcpc->vbus_level; + + if (tcpci_get_power_status(tcpc, &power_status) == 0) { + if (vbus_level != tcpc->vbus_level) { + TCPC_INFO("[Warning] ps_changed %d ->%d\n", + vbus_level, tcpc->vbus_level); + } + } + + return tcpci_check_vbus_valid(tcpc); +} + +int tcpci_check_vsafe0v( + struct tcpc_device *tcpc, bool detect_en) +{ + int ret = 0; + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + ret = (tcpc->vbus_level == TCPC_VBUS_SAFE0V); +#else + ret = (tcpc->vbus_level == TCPC_VBUS_INVALID); +#endif + + return ret; +} + +int tcpci_alert_status_clear( + struct tcpc_device *tcpc, uint32_t mask) +{ + PD_BUG_ON(tcpc->ops->alert_status_clear == NULL); + + return tcpc->ops->alert_status_clear(tcpc, mask); +} +EXPORT_SYMBOL(tcpci_alert_status_clear); + +int tcpci_fault_status_clear( + struct tcpc_device *tcpc, uint8_t status) +{ + PD_BUG_ON(tcpc->ops->fault_status_clear == NULL); + + return tcpc->ops->fault_status_clear(tcpc, status); +} + +int tcpci_set_alert_mask(struct tcpc_device *tcpc, uint32_t mask) +{ + int rv = 0; + + if (tcpc->ops->set_alert_mask) + return tcpc->ops->set_alert_mask(tcpc, mask); + + return rv; +} + +int tcpci_get_alert_mask( + struct tcpc_device *tcpc, uint32_t *mask) +{ + PD_BUG_ON(tcpc->ops->get_alert_mask == NULL); + + return tcpc->ops->get_alert_mask(tcpc, mask); +} + +int tcpci_get_alert_status( + struct tcpc_device *tcpc, uint32_t *alert) +{ + PD_BUG_ON(tcpc->ops->get_alert_status == NULL); + + return tcpc->ops->get_alert_status(tcpc, alert); +} + +int tcpci_get_fault_status( + struct tcpc_device *tcpc, uint8_t *fault) +{ + if (tcpc->ops->get_fault_status) + return tcpc->ops->get_fault_status(tcpc, fault); + + *fault = 0; + return 0; +} + +int tcpci_get_power_status( + struct tcpc_device *tcpc, uint16_t *pw_status) +{ + int ret; + + PD_BUG_ON(tcpc->ops->get_power_status == NULL); + + ret = tcpc->ops->get_power_status(tcpc, pw_status); + if (ret < 0) + return ret; + + tcpci_vbus_level_init(tcpc, *pw_status); + return 0; +} + +int tcpci_init(struct tcpc_device *tcpc, bool sw_reset) +{ + int ret; + uint16_t power_status; + + PD_BUG_ON(tcpc->ops->init == NULL); + + ret = tcpc->ops->init(tcpc, sw_reset); + if (ret < 0) + return ret; + + return tcpci_get_power_status(tcpc, &power_status); +} + +int tcpci_init_alert_mask(struct tcpc_device *tcpc) +{ + if (tcpc->ops->init_alert_mask) + return tcpc->ops->init_alert_mask(tcpc); + return 0; +} + +int tcpci_get_cc(struct tcpc_device *tcpc) +{ + int ret; + int cc1, cc2; + + PD_BUG_ON(tcpc->ops->get_cc == NULL); + + ret = tcpc->ops->get_cc(tcpc, &cc1, &cc2); + if (ret < 0) + return ret; + + if ((cc1 == tcpc->typec_remote_cc[0]) && + (cc2 == tcpc->typec_remote_cc[1])) { + return 0; + } + + tcpc->typec_remote_cc[0] = cc1; + tcpc->typec_remote_cc[1] = cc2; + + return 1; +} + +int tcpci_set_cc(struct tcpc_device *tcpc, int pull) +{ + PD_BUG_ON(tcpc->ops->set_cc == NULL); + +#ifdef CONFIG_USB_PD_DBG_ALWAYS_LOCAL_RP + if (pull == TYPEC_CC_RP) + pull = tcpc->typec_local_rp_level; +#endif /* CONFIG_USB_PD_DBG_ALWAYS_LOCAL_RP */ + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + if (pull == TYPEC_CC_DRP && tcpc->typec_legacy_cable) { +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + if (tcpc->typec_legacy_cable == 2) + pull = TYPEC_CC_RP; + else if (tcpc->typec_legacy_retry_wk > 1) + pull = TYPEC_CC_RP_3_0; + else +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ + pull = TYPEC_CC_RP_1_5; + TCPC_DBG2("LC->Toggling (%d)\n", pull); + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + + if (pull & TYPEC_CC_DRP) { + tcpc->typec_remote_cc[0] = + tcpc->typec_remote_cc[1] = + TYPEC_CC_DRP_TOGGLING; + } + + tcpc->typec_local_cc = pull; + return tcpc->ops->set_cc(tcpc, pull); +} + +int tcpci_set_polarity(struct tcpc_device *tcpc, int polarity) +{ + PD_BUG_ON(tcpc->ops->set_polarity == NULL); + + return tcpc->ops->set_polarity(tcpc, polarity); +} + +int tcpci_set_low_rp_duty(struct tcpc_device *tcpc, bool low_rp) +{ +#ifdef CONFIG_TYPEC_CAP_LOW_RP_DUTY + if (low_rp) + TCPC_INFO("low_rp_duty\n"); + + if (tcpc->ops->set_low_rp_duty) + return tcpc->ops->set_low_rp_duty(tcpc, low_rp); +#endif /* CONFIG_TYPEC_CAP_LOW_RP_DUTY */ + + return 0; +} + +int tcpci_set_vconn(struct tcpc_device *tcpc, int enable) +{ +#ifdef CONFIG_TCPC_SOURCE_VCONN + struct tcp_notify tcp_noti; + + if (tcpc->tcpc_source_vconn == enable) + return 0; + + tcpc->tcpc_source_vconn = enable; + + tcp_noti.en_state.en = enable != 0; + tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_VBUS, TCP_NOTIFY_SOURCE_VCONN); + + if (tcpc->ops->set_vconn) + return tcpc->ops->set_vconn(tcpc, enable); +#endif /* CONFIG_TCPC_SOURCE_VCONN */ + + return 0; +} + +int tcpci_is_low_power_mode(struct tcpc_device *tcpc) +{ + int rv = 1; + +#ifdef CONFIG_TCPC_LOW_POWER_MODE + if (tcpc->ops->is_low_power_mode) + rv = tcpc->ops->is_low_power_mode(tcpc); +#endif /* CONFIG_TCPC_LOW_POWER_MODE */ + + return rv; +} + +int tcpci_set_low_power_mode( + struct tcpc_device *tcpc, bool en, int pull) +{ + int rv = 0; + +#ifdef CONFIG_TCPC_LOW_POWER_MODE + if (tcpc->ops->set_low_power_mode) + rv = tcpc->ops->set_low_power_mode(tcpc, en, pull); +#endif /* CONFIG_TCPC_LOW_POWER_MODE */ + + return rv; +} + +int tcpci_set_watchdog(struct tcpc_device *tcpc, bool en) +{ + int rv = 0; + + if (tcpc->tcpc_flags & TCPC_FLAGS_WATCHDOG_EN) + if (tcpc->ops->set_watchdog) + rv = tcpc->ops->set_watchdog(tcpc, en); + + return rv; +} + +int tcpci_alert_vendor_defined_handler(struct tcpc_device *tcpc) +{ + int rv = 0; + + if (tcpc->ops->alert_vendor_defined_handler) + rv = tcpc->ops->alert_vendor_defined_handler(tcpc); + + return rv; +} + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC +int tcpci_is_vsafe0v(struct tcpc_device *tcpc) +{ + int rv = -ENOTSUPP; + + if (tcpc->ops->is_vsafe0v) + rv = tcpc->ops->is_vsafe0v(tcpc); + + return rv; +} +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + +#ifdef CONFIG_WATER_DETECTION +int tcpci_is_water_detected(struct tcpc_device *tcpc) +{ + if (tcpc->ops->is_water_detected) + return tcpc->ops->is_water_detected(tcpc); + return 0; +} + +int tcpci_set_water_protection(struct tcpc_device *tcpc, bool en) +{ + if (tcpc->ops->set_water_protection) + return tcpc->ops->set_water_protection(tcpc, en); + return 0; +} + +int tcpci_set_usbid_polling(struct tcpc_device *tcpc, bool en) +{ + if (tcpc->ops->set_usbid_polling) + return tcpc->ops->set_usbid_polling(tcpc, en); + return 0; +} + +int tcpci_notify_wd_status(struct tcpc_device *tcpc, bool water_detected) +{ + struct tcp_notify tcp_noti; + + tcp_noti.wd_status.water_detected = water_detected; + return tcpc_check_notify_time(tcpc, &tcp_noti, TCP_NOTIFY_IDX_MISC, + TCP_NOTIFY_WD_STATUS); +} +#endif /* CONFIG_WATER_DETECTION */ + +#ifdef CONFIG_CABLE_TYPE_DETECTION +int tcpci_notify_cable_type(struct tcpc_device *tcpc) +{ + struct tcp_notify tcp_noti; + + tcp_noti.cable_type.type = tcpc->typec_cable_type; + return tcpc_check_notify_time(tcpc, &tcp_noti, TCP_NOTIFY_IDX_MISC, + TCP_NOTIFY_CABLE_TYPE); +} +#endif /* CONFIG_CABLE_TYPE_DETECTION */ + +#ifdef CONFIG_USB_POWER_DELIVERY + +int tcpci_set_msg_header(struct tcpc_device *tcpc, + uint8_t power_role, uint8_t data_role) +{ + PD_BUG_ON(tcpc->ops->set_msg_header == NULL); + + return tcpc->ops->set_msg_header(tcpc, power_role, data_role); +} + +int tcpci_set_rx_enable(struct tcpc_device *tcpc, uint8_t enable) +{ + PD_BUG_ON(tcpc->ops->set_rx_enable == NULL); + + return tcpc->ops->set_rx_enable(tcpc, enable); +} + +int tcpci_protocol_reset(struct tcpc_device *tcpc) +{ + if (tcpc->ops->protocol_reset) + return tcpc->ops->protocol_reset(tcpc); + + return 0; +} + +int tcpci_get_message(struct tcpc_device *tcpc, + uint32_t *payload, uint16_t *head, enum tcpm_transmit_type *type) +{ + PD_BUG_ON(tcpc->ops->get_message == NULL); + + return tcpc->ops->get_message(tcpc, payload, head, type); +} + +int tcpci_transmit(struct tcpc_device *tcpc, + enum tcpm_transmit_type type, uint16_t header, const uint32_t *data) +{ + PD_BUG_ON(tcpc->ops->transmit == NULL); + + return tcpc->ops->transmit(tcpc, type, header, data); +} + +int tcpci_set_bist_test_mode(struct tcpc_device *tcpc, bool en) +{ + if (tcpc->ops->set_bist_test_mode) + return tcpc->ops->set_bist_test_mode(tcpc, en); + + return 0; +} + +int tcpci_set_bist_carrier_mode(struct tcpc_device *tcpc, uint8_t pattern) +{ + PD_BUG_ON(tcpc->ops->set_bist_carrier_mode == NULL); + + if (pattern) /* wait for GoodCRC */ + udelay(240); + + return tcpc->ops->set_bist_carrier_mode(tcpc, pattern); +} + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD +int tcpci_retransmit(struct tcpc_device *tcpc) +{ + PD_BUG_ON(tcpc->ops->retransmit == NULL); + + return tcpc->ops->retransmit(tcpc); +} +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + +int tcpci_notify_typec_state(struct tcpc_device *tcpc) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.typec_state.polarity = tcpc->typec_polarity; + tcp_noti.typec_state.old_state = tcpc->typec_attach_old; + tcp_noti.typec_state.new_state = tcpc->typec_attach_new; + tcp_noti.typec_state.rp_level = tcpc->typec_remote_rp_level; + tcp_noti.typec_state.local_rp_level = tcpc->typec_local_rp_level; + + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_USB, TCP_NOTIFY_TYPEC_STATE); + return ret; +} + +int tcpci_notify_role_swap( + struct tcpc_device *tcpc, uint8_t event, uint8_t role) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.swap_state.new_role = role; + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MISC, event); + return ret; +} + +int tcpci_notify_pd_state(struct tcpc_device *tcpc, uint8_t connect) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.pd_state.connected = connect; + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_USB, TCP_NOTIFY_PD_STATE); + return ret; +} + +int tcpci_set_intrst(struct tcpc_device *tcpc, bool en) +{ +#ifdef CONFIG_TCPC_INTRST_EN + if (tcpc->ops->set_intrst) + tcpc->ops->set_intrst(tcpc, en); +#endif /* CONFIG_TCPC_INTRST_EN */ + + return 0; +} + +int tcpci_enable_watchdog(struct tcpc_device *tcpc, bool en) +{ + if (!(tcpc->tcpc_flags & TCPC_FLAGS_WATCHDOG_EN)) + return 0; + + TCPC_DBG2("enable_WG: %d\n", en); + + if (tcpc->typec_watchdog == en) + return 0; + + mutex_lock(&tcpc->access_lock); + tcpc->typec_watchdog = en; + + if (tcpc->ops->set_watchdog) + tcpc->ops->set_watchdog(tcpc, en); + +#ifdef CONFIG_TCPC_INTRST_EN + if (!en || tcpc->attach_wake_lock->active) + tcpci_set_intrst(tcpc, en); +#endif /* CONFIG_TCPC_INTRST_EN */ + + mutex_unlock(&tcpc->access_lock); + + return 0; +} + +int tcpci_source_vbus( + struct tcpc_device *tcpc, uint8_t type, int mv, int ma) +{ + struct tcp_notify tcp_noti; + int ret; + +#ifdef CONFIG_USB_POWER_DELIVERY + if (type >= TCP_VBUS_CTRL_PD && + tcpc->pd_port.pe_data.pd_prev_connected) + type |= TCP_VBUS_CTRL_PD_DETECT; +#endif /* CONFIG_USB_POWER_DELIVERY */ + + if (ma < 0) { + if (mv != 0) { + switch (tcpc->typec_local_rp_level) { + case TYPEC_CC_RP_1_5: + ma = 1500; + break; + case TYPEC_CC_RP_3_0: + ma = 3000; + break; + default: + case TYPEC_CC_RP_DFT: + ma = CONFIG_TYPEC_SRC_CURR_DFT; + break; + } + } else + ma = 0; + } + + tcp_noti.vbus_state.ma = ma; + tcp_noti.vbus_state.mv = mv; + tcp_noti.vbus_state.type = type; + + tcpci_enable_watchdog(tcpc, mv != 0); + TCPC_DBG("source_vbus: %d mV, %d mA\n", mv, ma); + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_VBUS, TCP_NOTIFY_SOURCE_VBUS); + return ret; +} +EXPORT_SYMBOL(tcpci_source_vbus); + +int tcpci_sink_vbus( + struct tcpc_device *tcpc, uint8_t type, int mv, int ma) +{ + struct tcp_notify tcp_noti; + int ret; + +#ifdef CONFIG_USB_POWER_DELIVERY + if (type >= TCP_VBUS_CTRL_PD && + tcpc->pd_port.pe_data.pd_prev_connected) + type |= TCP_VBUS_CTRL_PD_DETECT; +#endif /* CONFIG_USB_POWER_DELIVERY */ + + if (ma < 0) { + if (mv != 0) { + switch (tcpc->typec_remote_rp_level) { + case TYPEC_CC_VOLT_SNK_1_5: + ma = 1500; + break; + case TYPEC_CC_VOLT_SNK_3_0: + ma = 3000; + break; + default: + case TYPEC_CC_VOLT_SNK_DFT: + ma = tcpc->typec_usb_sink_curr; + break; + } +#if CONFIG_TYPEC_SNK_CURR_LIMIT > 0 + if (ma > CONFIG_TYPEC_SNK_CURR_LIMIT) + ma = CONFIG_TYPEC_SNK_CURR_LIMIT; +#endif /* CONFIG_TYPEC_SNK_CURR_LIMIT */ + } else + ma = 0; + } + + tcp_noti.vbus_state.ma = ma; + tcp_noti.vbus_state.mv = mv; + tcp_noti.vbus_state.type = type; + + TCPC_DBG("sink_vbus: %d mV, %d mA\n", mv, ma); + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_VBUS, TCP_NOTIFY_SINK_VBUS); + return ret; +} + +int tcpci_disable_vbus_control(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_USE_DIS_VBUS_CTRL + struct tcp_notify tcp_noti; + int ret; + + TCPC_DBG("disable_vbus\n"); + tcpci_enable_watchdog(tcpc, false); + + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_VBUS, TCP_NOTIFY_DIS_VBUS_CTRL); + return ret; +#else + tcpci_sink_vbus(tcpc, TCP_VBUS_CTRL_REMOVE, TCPC_VBUS_SINK_0V, 0); + tcpci_source_vbus(tcpc, TCP_VBUS_CTRL_REMOVE, TCPC_VBUS_SOURCE_0V, 0); + return 0; +#endif /* CONFIG_TYPEC_USE_DIS_VBUS_CTRL */ +} + +int tcpci_notify_attachwait_state(struct tcpc_device *tcpc, bool as_sink) +{ +#ifdef CONFIG_TYPEC_NOTIFY_ATTACHWAIT + uint8_t notify = 0; + struct tcp_notify tcp_noti; + int ret; + +#ifdef CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SNK + if (as_sink) + notify = TCP_NOTIFY_ATTACHWAIT_SNK; +#endif /* CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SNK */ + +#ifdef CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SRC + if (!as_sink) + notify = TCP_NOTIFY_ATTACHWAIT_SRC; +#endif /* CONFIG_TYPEC_NOTIFY_ATTACHWAIT_SRC */ + + if (notify == 0) + return 0; + + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_VBUS, notify); + return ret; +#else + return 0; +#endif /* CONFIG_TYPEC_NOTIFY_ATTACHWAIT */ + +} + +int tcpci_enable_auto_discharge(struct tcpc_device *tcpc, bool en) +{ + int ret = 0; + +#ifdef CONFIG_TYPEC_CAP_AUTO_DISCHARGE +#ifdef CONFIG_TCPC_AUTO_DISCHARGE_IC + if (tcpc->typec_auto_discharge != en) { + tcpc->typec_auto_discharge = en; + if (tcpc->ops->set_auto_discharge) + ret = tcpc->ops->set_auto_discharge(tcpc, en); + } +#endif /* CONFIG_TCPC_AUTO_DISCHARGE_IC */ +#endif /* CONFIG_TYPEC_CAP_AUTO_DISCHARGE */ + + return ret; +} + +static int __tcpci_enable_force_discharge( + struct tcpc_device *tcpc, bool en, int mv) +{ + int ret = 0; + +#ifdef CONFIG_TYPEC_CAP_FORCE_DISCHARGE +#ifdef CONFIG_TCPC_FORCE_DISCHARGE_IC + if (tcpc->pd_force_discharge != en) { + tcpc->pd_force_discharge = en; + if (tcpc->ops->set_force_discharge) + ret = tcpc->ops->set_force_discharge(tcpc, en, mv); + } +#endif /* CONFIG_TCPC_FORCE_DISCHARGE_IC */ +#endif /* CONFIG_TYPEC_CAP_FORCE_DISCHARGE */ + + return ret; +} + +static int __tcpci_enable_ext_discharge(struct tcpc_device *tcpc, bool en) +{ + int ret = 0; + +#ifdef CONFIG_TCPC_EXT_DISCHARGE + struct tcp_notify tcp_noti; + + if (tcpc->typec_ext_discharge != en) { + tcpc->typec_ext_discharge = en; + tcp_noti.en_state.en = en; + TCPC_DBG("EXT-Discharge: %d\n", en); + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_VBUS, TCP_NOTIFY_EXT_DISCHARGE); + } +#endif /* CONFIG_TCPC_EXT_DISCHARGE */ + + return ret; +} + +int tcpci_enable_force_discharge(struct tcpc_device *tcpc, bool en, int mv) +{ + int ret = 0; + +#ifdef CONFIG_TYPEC_CAP_FORCE_DISCHARGE + ret = __tcpci_enable_force_discharge(tcpc, en, mv); +#ifdef CONFIG_TCPC_FORCE_DISCHARGE_EXT + ret = __tcpci_enable_ext_discharge(tcpc, en); +#endif /* CONFIG_TCPC_FORCE_DISCHARGE_EXT */ +#endif /* CONFIG_TYPEC_CAP_FORCE_DISCHARGE */ + + return ret; +} + +#ifdef CONFIG_USB_POWER_DELIVERY + +int tcpci_notify_hard_reset_state(struct tcpc_device *tcpc, uint8_t state) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.hreset_state.state = state; + + if (state >= TCP_HRESET_SIGNAL_SEND) + tcpc->pd_wait_hard_reset_complete = true; + else if (tcpc->pd_wait_hard_reset_complete) + tcpc->pd_wait_hard_reset_complete = false; + else + return 0; + + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MISC, TCP_NOTIFY_HARD_RESET_STATE); + return ret; +} + +int tcpci_enter_mode(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, uint32_t mode) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.mode_ctrl.svid = svid; + tcp_noti.mode_ctrl.ops = ops; + tcp_noti.mode_ctrl.mode = mode; + + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MODE, TCP_NOTIFY_ENTER_MODE); + return ret; +} + +int tcpci_exit_mode(struct tcpc_device *tcpc, uint16_t svid) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.mode_ctrl.svid = svid; + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MODE, TCP_NOTIFY_EXIT_MODE); + return ret; + +} + +#ifdef CONFIG_USB_PD_ALT_MODE + +int tcpci_report_hpd_state(struct tcpc_device *tcpc, uint32_t dp_status) +{ + struct tcp_notify tcp_noti; + struct dp_data *dp_data = pd_get_dp_data(&tcpc->pd_port); + + /* UFP_D to DFP_D only */ + + if (PD_DP_CFG_DFP_D(dp_data->local_config)) { + tcp_noti.ama_dp_hpd_state.irq = PD_VDO_DPSTS_HPD_IRQ(dp_status); + tcp_noti.ama_dp_hpd_state.state = + PD_VDO_DPSTS_HPD_LVL(dp_status); + tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MODE, TCP_NOTIFY_AMA_DP_HPD_STATE); + } + + return 0; +} + +int tcpci_dp_status_update(struct tcpc_device *tcpc, uint32_t dp_status) +{ + DP_INFO("Status0: 0x%x\n", dp_status); + tcpci_report_hpd_state(tcpc, dp_status); + return 0; +} + +int tcpci_dp_configure(struct tcpc_device *tcpc, uint32_t dp_config) +{ + struct tcp_notify tcp_noti; + int ret; + + DP_INFO("LocalCFG: 0x%x\n", dp_config); + + switch (dp_config & 0x03) { + case 0: + tcp_noti.ama_dp_state.sel_config = SW_USB; + break; + case MODE_DP_SNK: + tcp_noti.ama_dp_state.sel_config = SW_DFP_D; + tcp_noti.ama_dp_state.pin_assignment = (dp_config >> 8) & 0xff; + break; + case MODE_DP_SRC: + tcp_noti.ama_dp_state.sel_config = SW_UFP_D; + tcp_noti.ama_dp_state.pin_assignment = (dp_config >> 16) & 0xff; + break; + } + if (tcp_noti.ama_dp_state.pin_assignment == 0) + tcp_noti.ama_dp_state.pin_assignment = (dp_config >> 16) & 0xff; + + DP_INFO("pin assignment: 0x%x\n", + tcp_noti.ama_dp_state.pin_assignment); + tcp_noti.ama_dp_state.signal = (dp_config >> 2) & 0x0f; + tcp_noti.ama_dp_state.polarity = tcpc->typec_polarity; + tcp_noti.ama_dp_state.active = 1; + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MODE, TCP_NOTIFY_AMA_DP_STATE); + return ret; +} + +int tcpci_dp_attention(struct tcpc_device *tcpc, uint32_t dp_status) +{ + /* DFP_U : Not call this function during internal flow */ + struct tcp_notify tcp_noti; + + DP_INFO("Attention: 0x%x\n", dp_status); + tcp_noti.ama_dp_attention.state = (uint8_t) dp_status; + tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MODE, TCP_NOTIFY_AMA_DP_ATTENTION); + return tcpci_report_hpd_state(tcpc, dp_status); +} + +int tcpci_dp_notify_status_update_done( + struct tcpc_device *tcpc, uint32_t dp_status, bool ack) +{ + /* DFP_U : Not call this function during internal flow */ + DP_INFO("Status1: 0x%x, ack=%d\n", dp_status, ack); + return 0; +} + +int tcpci_dp_notify_config_start(struct tcpc_device *tcpc) +{ + /* DFP_U : Put signal & mux into the Safe State */ + struct tcp_notify tcp_noti; + + DP_INFO("ConfigStart\n"); + tcp_noti.ama_dp_state.sel_config = SW_USB; + tcp_noti.ama_dp_state.active = 0; + tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MODE, TCP_NOTIFY_AMA_DP_STATE); + return 0; +} + +int tcpci_dp_notify_config_done(struct tcpc_device *tcpc, + uint32_t local_cfg, uint32_t remote_cfg, bool ack) +{ + /* DFP_U : If DP success, + * internal flow will enter this function finally + */ + DP_INFO("ConfigDone, L:0x%x, R:0x%x, ack=%d\n", + local_cfg, remote_cfg, ack); + + if (ack) + tcpci_dp_configure(tcpc, local_cfg); + + return 0; +} + +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM +int tcpci_notify_uvdm(struct tcpc_device *tcpc, bool ack) +{ + struct tcp_notify tcp_noti; + struct pd_port *pd_port = &tcpc->pd_port; + + tcp_noti.uvdm_msg.ack = ack; + + if (ack) { + tcp_noti.uvdm_msg.uvdm_cnt = pd_port->uvdm_cnt; + tcp_noti.uvdm_msg.uvdm_svid = pd_port->uvdm_svid; + tcp_noti.uvdm_msg.uvdm_data = pd_port->uvdm_data; + } + + tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MODE, TCP_NOTIFY_UVDM); + return 0; +} +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +#ifdef CONFIG_USB_PD_ALT_MODE_RTDC +int tcpci_dc_notify_en_unlock(struct tcpc_device *tcpc) +{ + struct tcp_notify tcp_noti; + int ret; + + DC_INFO("DirectCharge en_unlock\n"); + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MODE, TCP_NOTIFY_DC_EN_UNLOCK); + return ret; +} +#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */ + +/* ---- Policy Engine (PD30) ---- */ + +#ifdef CONFIG_USB_PD_REV30 + +#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE +int tcpci_notify_alert(struct tcpc_device *tcpc, uint32_t ado) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.alert_msg.ado = ado; + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MISC, TCP_NOTIFY_ALERT); + return ret; +} +#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE +int tcpci_notify_status(struct tcpc_device *tcpc, struct pd_status *sdb) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.status_msg.sdb = sdb; + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MISC, TCP_NOTIFY_STATUS); + return ret; +} +#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */ + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO +int tcpci_notify_request_bat_info( + struct tcpc_device *tcpc, enum pd_battery_reference ref) +{ + struct tcp_notify tcp_noti; + int ret; + + tcp_noti.request_bat.ref = ref; + ret = tcpc_check_notify_time(tcpc, &tcp_noti, + TCP_NOTIFY_IDX_MISC, TCP_NOTIFY_REQUEST_BAT_INFO); + return ret; +} +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + +#endif /* CONFIG_USB_PD_REV30 */ + +#endif /* CONFIG_USB_POWER_DELIVERY */ diff --git a/drivers/usb/typec/mmi_tcpc/tcpci_alert.c b/drivers/usb/typec/mmi_tcpc/tcpci_alert.c new file mode 100644 index 000000000000..4c47638203a7 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpci_alert.c @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2021 Richtek Inc. + * + * TCPC Interface for alert handler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "inc/tcpci.h" +#include "inc/tcpci_typec.h" + +#ifdef CONFIG_USB_POWER_DELIVERY +#include "inc/tcpci_event.h" +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_DUAL_ROLE_USB_INTF +#include +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + +/* + * [BLOCK] TCPCI IRQ Handler + */ + +static int tcpci_alert_cc_changed(struct tcpc_device *tcpc) +{ + return tcpc_typec_handle_cc_change(tcpc); +} + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + +static inline int tcpci_alert_vsafe0v(struct tcpc_device *tcpc) +{ + tcpc_typec_handle_vsafe0v(tcpc); + +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_USB_PD_SAFE0V_DELAY + tcpc_enable_timer(tcpc, PD_TIMER_VSAFE0V_DELAY); +#else + pd_put_vbus_safe0v_event(tcpc); +#endif /* CONFIG_USB_PD_SAFE0V_DELAY */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + + return 0; +} + +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + +static inline void tcpci_vbus_level_init_v10( + struct tcpc_device *tcpc, uint16_t power_status) +{ + mutex_lock(&tcpc->access_lock); + + tcpc->vbus_level = + power_status & TCPC_REG_POWER_STATUS_VBUS_PRES ? + TCPC_VBUS_VALID : TCPC_VBUS_INVALID; + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + if (power_status & TCPC_REG_POWER_STATUS_EXT_VSAFE0V) { + if (tcpc->vbus_level == TCPC_VBUS_INVALID) + tcpc->vbus_level = TCPC_VBUS_SAFE0V; + else + TCPC_INFO("ps_confused: 0x%02x\n", power_status); + } +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + + mutex_unlock(&tcpc->access_lock); +} + +static inline void __tcpci_vbus_level_refresh(struct tcpc_device *tcpc) +{ + tcpc->vbus_level = tcpc->vbus_present ? TCPC_VBUS_VALID : + TCPC_VBUS_INVALID; + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + if (tcpc->vbus_safe0v) { + if (tcpc->vbus_level == TCPC_VBUS_INVALID) + tcpc->vbus_level = TCPC_VBUS_SAFE0V; + else + TCPC_INFO("ps_confused: %d\n", tcpc->vbus_level); + } +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ +} + +static inline void tcpci_vbus_level_refresh(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->access_lock); + __tcpci_vbus_level_refresh(tcpc); + mutex_unlock(&tcpc->access_lock); +} + +void tcpci_vbus_level_init(struct tcpc_device *tcpc, uint16_t status) +{ + if (tcpc->tcpc_flags & TCPC_FLAGS_ALERT_V10) { + tcpci_vbus_level_init_v10(tcpc, status); + return; + } + + mutex_lock(&tcpc->access_lock); + + tcpc->vbus_present = status & TCPC_REG_POWER_STATUS_VBUS_PRES ? + true : false; + __tcpci_vbus_level_refresh(tcpc); + mutex_unlock(&tcpc->access_lock); +} + +static inline int tcpci_alert_power_status_changed_v10(struct tcpc_device *tcpc) +{ + int rv = 0; + bool show_msg = true; + uint16_t power_status = 0; + + rv = tcpci_get_power_status(tcpc, &power_status); + if (rv < 0) + return rv; + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE + if (tcpc->pd_during_direct_charge && tcpc->vbus_level != 0) + show_msg = false; +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + + if (show_msg) + TCPC_INFO("ps_change=%d\n", tcpc->vbus_level); + + rv = tcpc_typec_handle_ps_change(tcpc, tcpc->vbus_level); + if (rv < 0) + return rv; + +#ifdef CONFIG_USB_POWER_DELIVERY + pd_put_vbus_changed_event(tcpc, true); +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + if (tcpc->vbus_level == TCPC_VBUS_SAFE0V) + rv = tcpci_alert_vsafe0v(tcpc); +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + + return rv; +} + +static inline int tcpci_vbus_level_changed(struct tcpc_device *tcpc) +{ + int rv = 0; + bool show_msg = true; + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE + if (tcpc->pd_during_direct_charge && tcpc->vbus_level != 0) + show_msg = false; +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + + if (show_msg) + TCPC_INFO("ps_change=%d\n", tcpc->vbus_level); + + rv = tcpc_typec_handle_ps_change(tcpc, tcpc->vbus_level); + if (rv < 0) + return rv; + +#ifdef CONFIG_USB_POWER_DELIVERY + pd_put_vbus_changed_event(tcpc, true); +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + if (tcpc->vbus_level == TCPC_VBUS_SAFE0V) + rv = tcpci_alert_vsafe0v(tcpc); +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + return rv; +} + +static int tcpci_alert_power_status_changed(struct tcpc_device *tcpc) +{ + int rv = 0; + uint16_t status = 0; + + if (tcpc->tcpc_flags & TCPC_FLAGS_ALERT_V10) + return tcpci_alert_power_status_changed_v10(tcpc); + + rv = tcpci_get_power_status(tcpc, &status); + if (rv < 0) + return rv; + + tcpc->vbus_present = (status & TCPC_REG_POWER_STATUS_VBUS_PRES) ? + true : false; + return rv; +} + +#ifdef CONFIG_USB_POWER_DELIVERY +static int tcpci_alert_tx_success(struct tcpc_device *tcpc) +{ + uint8_t tx_state; + + struct pd_event evt = { + .event_type = PD_EVT_CTRL_MSG, + .msg = PD_CTRL_GOOD_CRC, + .pd_msg = NULL, + }; + + mutex_lock(&tcpc->access_lock); + tx_state = tcpc->pd_transmit_state; + tcpc->pd_transmit_state = PD_TX_STATE_GOOD_CRC; + mutex_unlock(&tcpc->access_lock); + + if (tx_state == PD_TX_STATE_WAIT_CRC_VDM) + pd_put_vdm_event(tcpc, &evt, false); + else + pd_put_event(tcpc, &evt, false); + + return 0; +} + +static int tcpci_alert_tx_failed(struct tcpc_device *tcpc) +{ + uint8_t tx_state; + + mutex_lock(&tcpc->access_lock); + tx_state = tcpc->pd_transmit_state; + tcpc->pd_transmit_state = PD_TX_STATE_NO_GOOD_CRC; + mutex_unlock(&tcpc->access_lock); + + if (tx_state == PD_TX_STATE_WAIT_CRC_VDM) + vdm_put_hw_event(tcpc, PD_HW_TX_FAILED); + else + pd_put_hw_event(tcpc, PD_HW_TX_FAILED); + + return 0; +} + +static int tcpci_alert_tx_discard(struct tcpc_device *tcpc) +{ + uint8_t tx_state; + bool retry_crc_discard = false; + + mutex_lock(&tcpc->access_lock); + tx_state = tcpc->pd_transmit_state; + tcpc->pd_transmit_state = PD_TX_STATE_DISCARD; + mutex_unlock(&tcpc->access_lock); + + TCPC_INFO("Discard\n"); + + if (tx_state == PD_TX_STATE_WAIT_CRC_VDM) + pd_put_last_vdm_event(tcpc); + else { + retry_crc_discard = + (tcpc->tcpc_flags & + TCPC_FLAGS_RETRY_CRC_DISCARD) != 0; + + if (retry_crc_discard) { +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + tcpc->pd_discard_pending = true; + tcpc_enable_timer(tcpc, PD_TIMER_DISCARD); +#else + TCPC_ERR("RETRY_CRC_DISCARD\n"); +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + } else { + pd_put_hw_event(tcpc, PD_HW_TX_FAILED); + } + } + return 0; +} + +static int tcpci_alert_recv_msg(struct tcpc_device *tcpc) +{ + int retval; + struct pd_msg *pd_msg; + enum tcpm_transmit_type type; + + pd_msg = pd_alloc_msg(tcpc); + if (pd_msg == NULL) { + tcpci_alert_status_clear(tcpc, TCPC_REG_ALERT_RX_MASK); + return -EINVAL; + } + + retval = tcpci_get_message(tcpc, + pd_msg->payload, &pd_msg->msg_hdr, &type); + if (retval < 0) { + TCPC_INFO("recv_msg failed: %d\n", retval); + pd_free_msg(tcpc, pd_msg); + return retval; + } + + pd_msg->frame_type = (uint8_t) type; + pd_put_pd_msg_event(tcpc, pd_msg); + return 0; +} + +static int tcpci_alert_rx_overflow(struct tcpc_device *tcpc) +{ + int rv; + uint32_t alert_status; + + TCPC_INFO("RX_OVERFLOW\n"); + + rv = tcpci_get_alert_status(tcpc, &alert_status); + if (rv) + return rv; + + if (alert_status & TCPC_REG_ALERT_RX_STATUS) + return tcpci_alert_recv_msg(tcpc); + + return 0; +} + +static int tcpci_alert_recv_hard_reset(struct tcpc_device *tcpc) +{ + TCPC_INFO("HardResetAlert\n"); + pd_put_recv_hard_reset_event(tcpc); + tcpci_init_alert_mask(tcpc); + return 0; +} + +#endif /* CONFIG_USB_POWER_DELIVERY */ + +static int tcpci_alert_vendor_defined(struct tcpc_device *tcpc) +{ + tcpci_alert_vendor_defined_handler(tcpc); + return 0; +} + +static int tcpci_alert_fault(struct tcpc_device *tcpc) +{ + uint8_t status = 0; + + tcpci_get_fault_status(tcpc, &status); + TCPC_INFO("FaultAlert=0x%x\n", status); + tcpci_fault_status_clear(tcpc, status); + return 0; +} +EXPORT_SYMBOL(tcpci_alert); + +#ifdef CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG +static int tcpci_alert_wakeup(struct tcpc_device *tcpc) +{ + if (tcpc->tcpc_flags & TCPC_FLAGS_LPM_WAKEUP_WATCHDOG) { + TCPC_INFO("Wakeup\n"); + + if (tcpc->typec_remote_cc[0] == TYPEC_CC_DRP_TOGGLING) + tcpc_enable_wakeup_timer(tcpc, true); + } + + return 0; +} +#endif /* CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG */ + +#ifdef CONFIG_TYPEC_CAP_RA_DETACH +static int tcpci_alert_ra_detach(struct tcpc_device *tcpc) +{ + if (tcpc->tcpc_flags & TCPC_FLAGS_CHECK_RA_DETACHE) { + TCPC_DBG("RA_DETACH\n"); + + if (tcpc->typec_remote_cc[0] == TYPEC_CC_DRP_TOGGLING) + tcpc_typec_enter_lpm_again(tcpc); + } + + return 0; +} +#endif /* CONFIG_TYPEC_CAP_RA_DETACH */ + +struct tcpci_alert_handler { + uint32_t bit_mask; + int (*handler)(struct tcpc_device *tcpc); +}; + +#define DECL_TCPCI_ALERT_HANDLER(xbit, xhandler) {\ + .bit_mask = 1 << xbit,\ + .handler = xhandler, \ + } + +static const struct tcpci_alert_handler tcpci_alert_handlers[] = { + DECL_TCPCI_ALERT_HANDLER(15, tcpci_alert_vendor_defined), +#ifdef CONFIG_USB_POWER_DELIVERY + DECL_TCPCI_ALERT_HANDLER(4, tcpci_alert_tx_failed), + DECL_TCPCI_ALERT_HANDLER(5, tcpci_alert_tx_discard), + DECL_TCPCI_ALERT_HANDLER(6, tcpci_alert_tx_success), + DECL_TCPCI_ALERT_HANDLER(2, tcpci_alert_recv_msg), + DECL_TCPCI_ALERT_HANDLER(7, NULL), + DECL_TCPCI_ALERT_HANDLER(8, NULL), + DECL_TCPCI_ALERT_HANDLER(3, tcpci_alert_recv_hard_reset), + DECL_TCPCI_ALERT_HANDLER(10, tcpci_alert_rx_overflow), +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG + DECL_TCPCI_ALERT_HANDLER(16, tcpci_alert_wakeup), +#endif /* CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG */ + +#ifdef CONFIG_TYPEC_CAP_RA_DETACH + DECL_TCPCI_ALERT_HANDLER(21, tcpci_alert_ra_detach), +#endif /* CONFIG_TYPEC_CAP_RA_DETACH */ + + DECL_TCPCI_ALERT_HANDLER(9, tcpci_alert_fault), + DECL_TCPCI_ALERT_HANDLER(0, tcpci_alert_cc_changed), + DECL_TCPCI_ALERT_HANDLER(1, tcpci_alert_power_status_changed), +}; + +#ifdef CONFIG_USB_POWER_DELIVERY +static inline bool tcpci_check_hard_reset_complete( + struct tcpc_device *tcpc, uint32_t alert_status) +{ + if ((alert_status & TCPC_REG_ALERT_HRESET_SUCCESS) + == TCPC_REG_ALERT_HRESET_SUCCESS) { + pd_put_sent_hard_reset_event(tcpc); + return true; + } + + if (alert_status & TCPC_REG_ALERT_TX_DISCARDED) { + TCPC_INFO("HResetFailed\n"); + tcpci_transmit(tcpc, TCPC_TX_HARD_RESET, 0, NULL); + return false; + } + + return false; +} +#endif /* CONFIG_USB_POWER_DELIVERY */ +int tcpci_alert(struct tcpc_device *tcpc) +{ + int rv, i; + uint32_t alert_status; + uint32_t alert_mask; + + rv = tcpci_get_alert_status(tcpc, &alert_status); + if (rv) + return rv; + + rv = tcpci_get_alert_mask(tcpc, &alert_mask); + if (rv) + return rv; + + /* mask all alert */ + rv = tcpci_set_alert_mask(tcpc, 0); + if (rv) + return rv; + +#ifdef CONFIG_USB_PD_DBG_ALERT_STATUS + if (alert_status != 0) + TCPC_INFO("Alert:0x%04x, Mask:0x%04x\n", + alert_status, alert_mask); +#endif /* CONFIG_USB_PD_DBG_ALERT_STATUS */ + + alert_status &= alert_mask; + + tcpci_alert_status_clear(tcpc, + alert_status & (~TCPC_REG_ALERT_RX_MASK)); + + if (tcpc->typec_role == TYPEC_ROLE_UNKNOWN) + return 0; + + if ((tcpc->tcpc_flags & TCPC_FLAGS_ALERT_V10) && + (alert_status & TCPC_REG_ALERT_EXT_VBUS_80)) + alert_status |= TCPC_REG_ALERT_POWER_STATUS; + +#ifdef CONFIG_USB_POWER_DELIVERY + if (tcpc->pd_transmit_state == PD_TX_STATE_WAIT_HARD_RESET) { + tcpci_check_hard_reset_complete(tcpc, alert_status); + alert_status &= ~TCPC_REG_ALERT_TX_MASK; + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifndef CONFIG_USB_PD_DBG_SKIP_ALERT_HANDLER + for (i = 0; i < ARRAY_SIZE(tcpci_alert_handlers); i++) { + if (tcpci_alert_handlers[i].bit_mask & alert_status) { + if (tcpci_alert_handlers[i].handler != 0) + tcpci_alert_handlers[i].handler(tcpc); + } + } +#endif /* CONFIG_USB_PD_DBG_SKIP_ALERT_HANDLER */ + + /* unmask alert */ + rv = tcpci_set_alert_mask(tcpc, alert_mask); + if (rv) + return rv; + + if (tcpc->tcpc_flags & TCPC_FLAGS_ALERT_V10) + return 0; + + tcpci_vbus_level_refresh(tcpc); + tcpci_vbus_level_changed(tcpc); + return 0; +} + +/* + * [BLOCK] TYPEC device changed + */ + +static inline void tcpci_attach_wake_lock(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TCPC_ATTACH_WAKE_LOCK_TOUT + __pm_wakeup_event(tcpc->attach_wake_lock, + CONFIG_TCPC_ATTACH_WAKE_LOCK_TOUT); +#else + __pm_stay_awake(tcpc->attach_wake_lock); +#endif /* CONFIG_TCPC_ATTACH_WAKE_LOCK_TOUT */ +} + +int tcpci_set_wake_lock( + struct tcpc_device *tcpc, bool pd_lock, bool user_lock) +{ + bool ori_lock, new_lock; + + if (tcpc->wake_lock_pd && tcpc->wake_lock_user) + ori_lock = true; + else + ori_lock = false; + + if (pd_lock && user_lock) + new_lock = true; + else + new_lock = false; + + if (new_lock != ori_lock) { + if (new_lock) { + TCPC_DBG("wake_lock=1\n"); + tcpci_attach_wake_lock(tcpc); + if (tcpc->typec_watchdog) + tcpci_set_intrst(tcpc, true); + } else { + TCPC_DBG("wake_lock=0\n"); + if (tcpc->typec_watchdog) + tcpci_set_intrst(tcpc, false); + __pm_relax(tcpc->attach_wake_lock); + } + return 1; + } + + return 0; +} + +static inline int tcpci_set_wake_lock_pd( + struct tcpc_device *tcpc, bool pd_lock) +{ + uint8_t wake_lock_pd; + + mutex_lock(&tcpc->access_lock); + + wake_lock_pd = tcpc->wake_lock_pd; + + if (pd_lock) + wake_lock_pd++; + else if (wake_lock_pd > 0) + wake_lock_pd--; + + if (wake_lock_pd == 0) + __pm_wakeup_event(tcpc->detach_wake_lock, 5000); + + tcpci_set_wake_lock(tcpc, wake_lock_pd, tcpc->wake_lock_user); + + if (wake_lock_pd == 1) + __pm_relax(tcpc->detach_wake_lock); + + tcpc->wake_lock_pd = wake_lock_pd; + mutex_unlock(&tcpc->access_lock); + return 0; +} + +static inline int tcpci_report_usb_port_attached(struct tcpc_device *tcpc) +{ + TCPC_INFO("usb_port_attached\n"); + +#ifdef CONFIG_DUAL_ROLE_USB_INTF + switch (tcpc->typec_attach_new) { + case TYPEC_ATTACHED_SNK: + case TYPEC_ATTACHED_CUSTOM_SRC: + case TYPEC_ATTACHED_NORP_SRC: + tcpc->dual_role_pr = DUAL_ROLE_PROP_PR_SNK; + tcpc->dual_role_dr = DUAL_ROLE_PROP_DR_DEVICE; + tcpc->dual_role_mode = DUAL_ROLE_PROP_MODE_UFP; + tcpc->dual_role_vconn = DUAL_ROLE_PROP_VCONN_SUPPLY_NO; + break; + case TYPEC_ATTACHED_SRC: + tcpc->dual_role_pr = DUAL_ROLE_PROP_PR_SRC; + tcpc->dual_role_dr = DUAL_ROLE_PROP_DR_HOST; + tcpc->dual_role_mode = DUAL_ROLE_PROP_MODE_DFP; + tcpc->dual_role_vconn = DUAL_ROLE_PROP_VCONN_SUPPLY_YES; + break; + default: + break; + } + dual_role_instance_changed(tcpc->dr_usb); +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + + tcpci_set_wake_lock_pd(tcpc, true); + +#ifdef CONFIG_USB_POWER_DELIVERY + +#ifdef CONFIG_USB_PD_DISABLE_PE + if (tcpc->disable_pe) + return 0; +#endif /* CONFIG_USB_PD_DISABLE_PE */ + + if (tcpc->pd_inited_flag) + pd_put_cc_attached_event(tcpc, tcpc->typec_attach_new); +#endif /* CONFIG_USB_POWER_DLEIVERY */ + + return 0; +} + +static inline int tcpci_report_usb_port_detached(struct tcpc_device *tcpc) +{ + TCPC_INFO("usb_port_detached\n"); + +#ifdef CONFIG_DUAL_ROLE_USB_INTF + tcpc->dual_role_pr = DUAL_ROLE_PROP_PR_NONE; + tcpc->dual_role_dr = DUAL_ROLE_PROP_DR_NONE; + tcpc->dual_role_mode = DUAL_ROLE_PROP_MODE_NONE; + tcpc->dual_role_vconn = DUAL_ROLE_PROP_VCONN_SUPPLY_NO; + dual_role_instance_changed(tcpc->dr_usb); +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + +#ifdef CONFIG_USB_POWER_DELIVERY + if (tcpc->pd_inited_flag) + pd_put_cc_detached_event(tcpc); + else { + pd_event_buf_reset(tcpc); + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_PE_IDLE); + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + + tcpci_set_wake_lock_pd(tcpc, false); + return 0; +} + +int tcpci_report_usb_port_changed(struct tcpc_device *tcpc) +{ + tcpci_notify_typec_state(tcpc); + + if (tcpc->typec_attach_old == TYPEC_UNATTACHED) + tcpci_report_usb_port_attached(tcpc); + else if (tcpc->typec_attach_new == TYPEC_UNATTACHED) + tcpci_report_usb_port_detached(tcpc); + else + TCPC_DBG2("TCPC Attach Again\n"); + + return 0; +} +EXPORT_SYMBOL(tcpci_report_usb_port_changed); + +/* + * [BLOCK] TYPEC power control changed + */ + +static inline int tcpci_report_power_control_on(struct tcpc_device *tcpc) +{ + tcpci_set_wake_lock_pd(tcpc, true); + + mutex_lock(&tcpc->access_lock); + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_DISCHARGE); + tcpci_enable_auto_discharge(tcpc, true); + tcpci_enable_force_discharge(tcpc, false, 0); + mutex_unlock(&tcpc->access_lock); + + return 0; +} + +static inline int tcpci_report_power_control_off(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->access_lock); + tcpci_enable_force_discharge(tcpc, true, 0); + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_DISCHARGE); + mutex_unlock(&tcpc->access_lock); + + tcpci_set_wake_lock_pd(tcpc, false); + return 0; +} + +int tcpci_report_power_control(struct tcpc_device *tcpc, bool en) +{ + if (tcpc->typec_power_ctrl == en) + return 0; + + tcpc->typec_power_ctrl = en; + + if (en) + tcpci_report_power_control_on(tcpc); + else + tcpci_report_power_control_off(tcpc); + + return 0; +} diff --git a/drivers/usb/typec/mmi_tcpc/tcpci_core.c b/drivers/usb/typec/mmi_tcpc/tcpci_core.c new file mode 100644 index 000000000000..fe844ef5a741 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpci_core.c @@ -0,0 +1,980 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Richtek TypeC Port Control Interface Core Driver + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inc/tcpci.h" +#include "inc/tcpci_typec.h" + +#ifdef CONFIG_USB_POWER_DELIVERY +#include "pd_dpm_prv.h" +#include "inc/tcpm.h" +#ifdef CONFIG_RECV_BAT_ABSENT_NOTIFY +#include "mtk_battery.h" +#endif /* CONFIG_RECV_BAT_ABSENT_NOTIFY */ +#endif /* CONFIG_USB_POWER_DELIVERY */ +#include "inc/pd_dbg_info.h" +#include "inc/rt-regmap.h" + +#define TCPC_CORE_VERSION "2.0.14_G" + +static ssize_t tcpc_show_property(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t tcpc_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define TCPC_DEVICE_ATTR(_name, _mode) \ +{ \ + .attr = { .name = #_name, .mode = _mode }, \ + .show = tcpc_show_property, \ + .store = tcpc_store_property, \ +} + +struct class *tcpc_class; +EXPORT_SYMBOL_GPL(tcpc_class); + +static struct device_type tcpc_dev_type; + +static struct device_attribute tcpc_device_attributes[] = { + TCPC_DEVICE_ATTR(role_def, 0444), + TCPC_DEVICE_ATTR(rp_lvl, 0444), + TCPC_DEVICE_ATTR(pd_test, 0664), + TCPC_DEVICE_ATTR(info, 0444), + TCPC_DEVICE_ATTR(timer, 0664), + TCPC_DEVICE_ATTR(caps_info, 0444), + TCPC_DEVICE_ATTR(pe_ready, 0444), +}; + +enum { + TCPC_DESC_ROLE_DEF = 0, + TCPC_DESC_RP_LEVEL, + TCPC_DESC_PD_TEST, + TCPC_DESC_INFO, + TCPC_DESC_TIMER, + TCPC_DESC_CAP_INFO, + TCPC_DESC_PE_READY, +}; + +static struct attribute *__tcpc_attrs[ARRAY_SIZE(tcpc_device_attributes) + 1]; +static struct attribute_group tcpc_attr_group = { + .attrs = __tcpc_attrs, +}; + +static const struct attribute_group *tcpc_attr_groups[] = { + &tcpc_attr_group, + NULL, +}; + +static const char * const role_text[] = { + "SNK Only", + "SRC Only", + "DRP", + "Try.SRC", + "Try.SNK", +}; + +static ssize_t tcpc_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tcpc_device *tcpc = to_tcpc_device(dev); + const ptrdiff_t offset = attr - tcpc_device_attributes; + int i = 0; +#ifdef CONFIG_USB_POWER_DELIVERY + struct pe_data *pe_data; + struct pd_port *pd_port; + struct tcpm_power_cap_val cap; +#endif /* CONFIG_USB_POWER_DELIVERY */ + + switch (offset) { +#ifdef CONFIG_USB_POWER_DELIVERY + case TCPC_DESC_CAP_INFO: + pd_port = &tcpc->pd_port; + pe_data = &pd_port->pe_data; + snprintf(buf+strlen(buf), 256, "%s = %d\n%s = %d\n", + "local_selected_cap", + pe_data->local_selected_cap, + "remote_selected_cap", + pe_data->remote_selected_cap); + + snprintf(buf+strlen(buf), 256, "%s\n", + "local_src_cap(type, vmin, vmax, oper)"); + for (i = 0; i < pd_port->local_src_cap.nr; i++) { + tcpm_extract_power_cap_val( + pd_port->local_src_cap.pdos[i], + &cap); + snprintf(buf+strlen(buf), 256, "%d %d %d %d\n", + cap.type, cap.min_mv, cap.max_mv, cap.ma); + } + snprintf(buf+strlen(buf), 256, "%s\n", + "local_snk_cap(type, vmin, vmax, ioper)"); + for (i = 0; i < pd_port->local_snk_cap.nr; i++) { + tcpm_extract_power_cap_val( + pd_port->local_snk_cap.pdos[i], + &cap); + snprintf(buf+strlen(buf), 256, "%d %d %d %d\n", + cap.type, cap.min_mv, cap.max_mv, cap.ma); + } + snprintf(buf+strlen(buf), 256, "%s\n", + "remote_src_cap(type, vmin, vmax, ioper)"); + for (i = 0; i < pe_data->remote_src_cap.nr; i++) { + tcpm_extract_power_cap_val( + pe_data->remote_src_cap.pdos[i], + &cap); + snprintf(buf+strlen(buf), 256, "%d %d %d %d\n", + cap.type, cap.min_mv, cap.max_mv, cap.ma); + } + snprintf(buf+strlen(buf), 256, "%s\n", + "remote_snk_cap(type, vmin, vmax, ioper)"); + for (i = 0; i < pe_data->remote_snk_cap.nr; i++) { + tcpm_extract_power_cap_val( + pe_data->remote_snk_cap.pdos[i], + &cap); + snprintf(buf+strlen(buf), 256, "%d %d %d %d\n", + cap.type, cap.min_mv, cap.max_mv, cap.ma); + } + break; +#endif /* CONFIG_USB_POWER_DELIVERY */ + case TCPC_DESC_ROLE_DEF: + snprintf(buf, 256, "%s\n", role_text[tcpc->desc.role_def]); + break; + case TCPC_DESC_RP_LEVEL: + if (tcpc->typec_local_rp_level == TYPEC_CC_RP_DFT) + snprintf(buf, 256, "%s\n", "Default"); + else if (tcpc->typec_local_rp_level == TYPEC_CC_RP_1_5) + snprintf(buf, 256, "%s\n", "1.5"); + else if (tcpc->typec_local_rp_level == TYPEC_CC_RP_3_0) + snprintf(buf, 256, "%s\n", "3.0"); + break; + case TCPC_DESC_PD_TEST: + snprintf(buf, 256, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + "1: pr_swap", "2: dr_swap", "3: vconn_swap", + "4: soft reset", "5: hard reset", + "6: get_src_cap", "7: get_sink_cap", + "8: discover_id", "9: discover_cable"); + break; + case TCPC_DESC_INFO: + i += snprintf(buf + i, + 256, "|^|==( %s info )==|^|\n", tcpc->desc.name); + i += snprintf(buf + i, + 256, "role = %s\n", role_text[tcpc->desc.role_def]); + if (tcpc->typec_local_rp_level == TYPEC_CC_RP_DFT) + i += snprintf(buf + i, 256, "rplvl = %s\n", "Default"); + else if (tcpc->typec_local_rp_level == TYPEC_CC_RP_1_5) + i += snprintf(buf + i, 256, "rplvl = %s\n", "1.5"); + else if (tcpc->typec_local_rp_level == TYPEC_CC_RP_3_0) + i += snprintf(buf + i, 256, "rplvl = %s\n", "3.0"); + break; +#ifdef CONFIG_USB_POWER_DELIVERY + case TCPC_DESC_PE_READY: + pd_port = &tcpc->pd_port; + if (pd_port->pe_data.pe_ready) + snprintf(buf, 256, "%s\n", "yes"); + else + snprintf(buf, 256, "%s\n", "no"); + break; +#endif + default: + break; + } + return strlen(buf); +} + +static int get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (kstrtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } + else + return -EINVAL; + } + return 0; +} + +static ssize_t tcpc_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef CONFIG_USB_POWER_DELIVERY + uint8_t role; +#endif /* CONFIG_USB_POWER_DELIVERY */ + + struct tcpc_device *tcpc = to_tcpc_device(dev); + const ptrdiff_t offset = attr - tcpc_device_attributes; + int ret; + long int val; + + switch (offset) { + case TCPC_DESC_ROLE_DEF: + ret = get_parameters((char *)buf, &val, 1); + if (ret < 0) { + dev_err(dev, "get parameters fail\n"); + return -EINVAL; + } + + tcpm_typec_change_role(tcpc, val); + break; + case TCPC_DESC_TIMER: + ret = get_parameters((char *)buf, &val, 1); + if (ret < 0) { + dev_err(dev, "get parameters fail\n"); + return -EINVAL; + } + #ifdef CONFIG_USB_POWER_DELIVERY + if (val > 0 && val <= PD_PE_TIMER_END_ID) + pd_enable_timer(&tcpc->pd_port, val); + else if (val > PD_PE_TIMER_END_ID && val < PD_TIMER_NR) + tcpc_enable_timer(tcpc, val); + #else + if (val > 0 && val < PD_TIMER_NR) + tcpc_enable_timer(tcpc, val); + #endif /* CONFIG_USB_POWER_DELIVERY */ + break; + #ifdef CONFIG_USB_POWER_DELIVERY + case TCPC_DESC_PD_TEST: + ret = get_parameters((char *)buf, &val, 1); + if (ret < 0) { + dev_err(dev, "get parameters fail\n"); + return -EINVAL; + } + switch (val) { + case 1: /* Power Role Swap */ + role = tcpm_inquire_pd_power_role(tcpc); + if (role == PD_ROLE_SINK) + role = PD_ROLE_SOURCE; + else + role = PD_ROLE_SINK; + tcpm_dpm_pd_power_swap(tcpc, role, NULL); + break; + case 2: /* Data Role Swap */ + role = tcpm_inquire_pd_data_role(tcpc); + if (role == PD_ROLE_UFP) + role = PD_ROLE_DFP; + else + role = PD_ROLE_UFP; + tcpm_dpm_pd_data_swap(tcpc, role, NULL); + break; + case 3: /* Vconn Swap */ + role = tcpm_inquire_pd_vconn_role(tcpc); + if (role == PD_ROLE_VCONN_OFF) + role = PD_ROLE_VCONN_ON; + else + role = PD_ROLE_VCONN_OFF; + tcpm_dpm_pd_vconn_swap(tcpc, role, NULL); + break; + case 4: /* Software Reset */ + tcpm_dpm_pd_soft_reset(tcpc, NULL); + break; + case 5: /* Hardware Reset */ + tcpm_dpm_pd_hard_reset(tcpc, NULL); + break; + case 6: + tcpm_dpm_pd_get_source_cap(tcpc, NULL); + break; + case 7: + tcpm_dpm_pd_get_sink_cap(tcpc, NULL); + break; + case 8: + tcpm_dpm_vdm_discover_id(tcpc, NULL); + break; + case 9: + tcpm_dpm_vdm_discover_cable(tcpc, NULL); + break; + default: + break; + } + break; + #endif /* CONFIG_USB_POWER_DELIVERY */ + default: + break; + } + return count; +} + +static int tcpc_match_device_by_name(struct device *dev, const void *data) +{ + const char *name = data; + struct tcpc_device *tcpc = dev_get_drvdata(dev); + + return strcmp(tcpc->desc.name, name) == 0; +} + +struct tcpc_device *tcpc_dev_get_by_name(const char *name) +{ + struct device *dev = class_find_device(tcpc_class, + NULL, (const void *)name, tcpc_match_device_by_name); + return dev ? dev_get_drvdata(dev) : NULL; +} +EXPORT_SYMBOL(tcpc_dev_get_by_name); + +static void tcpc_device_release(struct device *dev) +{ + struct tcpc_device *tcpc = to_tcpc_device(dev); + + pr_info("%s : %s device release\n", __func__, dev_name(dev)); + PD_BUG_ON(tcpc == NULL); + /* Un-init pe thread */ +#ifdef CONFIG_USB_POWER_DELIVERY + tcpci_event_deinit(tcpc); +#endif /* CONFIG_USB_POWER_DELIVERY */ + /* Un-init timer thread */ + tcpci_timer_deinit(tcpc); + /* Un-init Mutex */ + /* Do initialization */ +} + +static void tcpc_init_work(struct work_struct *work); +static void tcpc_event_init_work(struct work_struct *work); + +struct tcpc_device *tcpc_device_register(struct device *parent, + struct tcpc_desc *tcpc_desc, struct tcpc_ops *ops, void *drv_data) +{ + struct tcpc_device *tcpc; + int ret = 0, i = 0; + + pr_info("%s register tcpc device (%s)\n", __func__, tcpc_desc->name); + tcpc = devm_kzalloc(parent, sizeof(*tcpc), GFP_KERNEL); + if (!tcpc) { + pr_err("%s : allocate tcpc memeory failed\n", __func__); + return NULL; + } + + tcpc->evt_wq = alloc_ordered_workqueue("%s", 0, tcpc_desc->name); + for (i = 0; i < TCP_NOTIFY_IDX_NR; i++) + srcu_init_notifier_head(&tcpc->evt_nh[i]); + + mutex_init(&tcpc->access_lock); + mutex_init(&tcpc->typec_lock); + mutex_init(&tcpc->timer_lock); + mutex_init(&tcpc->mr_lock); + sema_init(&tcpc->timer_enable_mask_lock, 1); + spin_lock_init(&tcpc->timer_tick_lock); + + tcpc->dev.class = tcpc_class; + tcpc->dev.type = &tcpc_dev_type; + tcpc->dev.parent = parent; + tcpc->dev.release = tcpc_device_release; + dev_set_drvdata(&tcpc->dev, tcpc); + tcpc->drv_data = drv_data; + dev_set_name(&tcpc->dev, "%s", tcpc_desc->name); + tcpc->desc = *tcpc_desc; + tcpc->ops = ops; + tcpc->typec_local_rp_level = tcpc_desc->rp_lvl; + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + tcpc->tcpc_vconn_supply = tcpc_desc->vconn_supply; +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + + device_set_of_node_from_dev(&tcpc->dev, parent); + + ret = device_register(&tcpc->dev); + if (ret) { + kfree(tcpc); + return ERR_PTR(ret); + } + + INIT_DELAYED_WORK(&tcpc->init_work, tcpc_init_work); + INIT_DELAYED_WORK(&tcpc->event_init_work, tcpc_event_init_work); + + /* If system support "WAKE_LOCK_IDLE", + * please use it instead of "WAKE_LOCK_SUSPEND" + */ + tcpc->attach_wake_lock = + wakeup_source_register(&tcpc->dev, "tcpc_attach_wake_lock"); + tcpc->detach_wake_lock = + wakeup_source_register(&tcpc->dev, "tcpc_detach_wake_lock"); + + tcpci_timer_init(tcpc); +#ifdef CONFIG_USB_POWER_DELIVERY + pd_core_init(tcpc); +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_DUAL_ROLE_USB_INTF + ret = tcpc_dual_role_phy_init(tcpc); + if (ret < 0) + dev_err(&tcpc->dev, "dual role usb init fail\n"); +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + + return tcpc; +} +EXPORT_SYMBOL(tcpc_device_register); + +int tcpc_device_irq_enable(struct tcpc_device *tcpc) +{ + int ret; + + if (!tcpc->ops->init) { + pr_notice("%s Please implment tcpc ops init function\n", + __func__); + return -EINVAL; + } + + tcpci_lock_typec(tcpc); + ret = tcpci_init(tcpc, false); + if (ret < 0) { + tcpci_unlock_typec(tcpc); + pr_err("%s tcpc init fail\n", __func__); + return ret; + } + + ret = tcpc_typec_init(tcpc, tcpc->desc.role_def + 1); + tcpci_unlock_typec(tcpc); + if (ret < 0) { + pr_err("%s : tcpc typec init fail\n", __func__); + return ret; + } + + schedule_delayed_work( + &tcpc->event_init_work, msecs_to_jiffies(10*1000)); + + pr_info("%s : tcpc irq enable OK!\n", __func__); + return 0; +} +EXPORT_SYMBOL(tcpc_device_irq_enable); + +#ifdef CONFIG_USB_PD_REV30 +static void bat_update_work_func(struct work_struct *work) +{ + struct tcpc_device *tcpc = container_of(work, + struct tcpc_device, bat_update_work.work); + union power_supply_propval value; + int ret; + + ret = power_supply_get_property( + tcpc->bat_psy, POWER_SUPPLY_PROP_CAPACITY, &value); + if (ret == 0) { + TCPC_INFO("%s battery update soc = %d\n", + __func__, value.intval); + tcpc->bat_soc = value.intval; + } else + TCPC_ERR("%s get battery capacity fail\n", __func__); + + ret = power_supply_get_property(tcpc->bat_psy, + POWER_SUPPLY_PROP_STATUS, &value); + if (ret == 0) { + if (value.intval == POWER_SUPPLY_STATUS_CHARGING) { + TCPC_INFO("%s Battery Charging\n", __func__); + tcpc->charging_status = BSDO_BAT_INFO_CHARGING; + } else if (value.intval == POWER_SUPPLY_STATUS_DISCHARGING) { + TCPC_INFO("%s Battery Discharging\n", __func__); + tcpc->charging_status = BSDO_BAT_INFO_DISCHARGING; + } else { + TCPC_INFO("%s Battery Idle\n", __func__); + tcpc->charging_status = BSDO_BAT_INFO_IDLE; + } + } + if (ret < 0) + TCPC_ERR("%s get battery charger now fail\n", __func__); + + tcpm_update_bat_status_soc(tcpc, + PD_BAT_REF_FIXED0, tcpc->charging_status, tcpc->bat_soc * 10); +} + +static int bat_nb_call_func( + struct notifier_block *nb, unsigned long val, void *v) +{ + struct tcpc_device *tcpc = container_of(nb, struct tcpc_device, bat_nb); + struct power_supply *psy = (struct power_supply *)v; + + if (!tcpc) { + TCPC_ERR("%s tcpc is null\n", __func__); + return NOTIFY_OK; + } + + if (val == PSY_EVENT_PROP_CHANGED && + strcmp(psy->desc->name, "battery") == 0) + schedule_delayed_work(&tcpc->bat_update_work, 0); + return NOTIFY_OK; +} +#endif /* CONFIG_USB_PD_REV30 */ + +static void tcpc_event_init_work(struct work_struct *work) +{ +#ifdef CONFIG_USB_POWER_DELIVERY + struct tcpc_device *tcpc = container_of( + work, struct tcpc_device, event_init_work.work); +#ifdef CONFIG_USB_PD_REV30 + int retval; +#endif /* CONFIG_USB_PD_REV30 */ + + tcpci_lock_typec(tcpc); + tcpci_event_init(tcpc); +#ifdef CONFIG_USB_PD_WAIT_BC12 + tcpc->usb_psy = power_supply_get_by_name("usb"); + if (!tcpc->usb_psy) { + tcpci_unlock_typec(tcpc); + TCPC_ERR("%s get usb psy fail\n", __func__); + return; + } +#endif /* CONFIG_USB_PD_WAIT_BC12 */ + tcpc->pd_inited_flag = 1; + pr_info("%s typec attach new = %d\n", + __func__, tcpc->typec_attach_new); + if (tcpc->typec_attach_new) + pd_put_cc_attached_event(tcpc, tcpc->typec_attach_new); + tcpci_unlock_typec(tcpc); + +#ifdef CONFIG_USB_PD_REV30 + INIT_DELAYED_WORK(&tcpc->bat_update_work, bat_update_work_func); + tcpc->bat_psy = power_supply_get_by_name("battery"); + if (!tcpc->bat_psy) { + TCPC_ERR("%s get battery psy fail\n", __func__); + return; + } + tcpc->charging_status = BSDO_BAT_INFO_IDLE; + tcpc->bat_soc = 0; + tcpc->bat_nb.notifier_call = bat_nb_call_func; + tcpc->bat_nb.priority = 0; + retval = power_supply_reg_notifier(&tcpc->bat_nb); + if (retval < 0) + pr_err("%s register power supply notifier fail\n", __func__); +#endif /* CONFIG_USB_PD_REV30 */ + +#endif /* CONFIG_USB_POWER_DELIVERY */ +} + +static void tcpc_init_work(struct work_struct *work) +{ + struct tcpc_device *tcpc = container_of( + work, struct tcpc_device, init_work.work); + +#ifndef CONFIG_TCPC_NOTIFIER_LATE_SYNC + if (tcpc->desc.notifier_supply_num == 0) + return; +#endif + pr_info("%s force start\n", __func__); + + tcpc->desc.notifier_supply_num = 0; + tcpc_device_irq_enable(tcpc); +} + +int tcpc_schedule_init_work(struct tcpc_device *tcpc) +{ +#ifndef CONFIG_TCPC_NOTIFIER_LATE_SYNC + if (tcpc->desc.notifier_supply_num == 0) + return tcpc_device_irq_enable(tcpc); + + pr_info("%s wait %d num\n", __func__, tcpc->desc.notifier_supply_num); + + schedule_delayed_work( + &tcpc->init_work, msecs_to_jiffies(30*1000)); +#endif + return 0; +} +EXPORT_SYMBOL(tcpc_schedule_init_work); + +struct tcp_notifier_block_wrapper { + struct notifier_block stub_nb; + struct notifier_block *action_nb; +}; + +static int tcp_notifier_func_stub(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct tcp_notifier_block_wrapper *nb_wrapper = + container_of(nb, struct tcp_notifier_block_wrapper, stub_nb); + struct notifier_block *action_nb = nb_wrapper->action_nb; + + return nb_wrapper->action_nb->notifier_call(action_nb, action, data); +} + +struct tcpc_managed_res { + void *res; + void *key; + int prv_id; + struct tcpc_managed_res *next; +}; + + +static int __add_wrapper_to_managed_res_list(struct tcpc_device *tcp_dev, + void *res, void *key, int prv_id) +{ + struct tcpc_managed_res *tail; + struct tcpc_managed_res *mres; + + mres = devm_kzalloc(&tcp_dev->dev, sizeof(*mres), GFP_KERNEL); + if (!mres) + return -ENOMEM; + mres->res = res; + mres->key = key; + mres->prv_id = prv_id; + mutex_lock(&tcp_dev->mr_lock); + tail = tcp_dev->mr_head; + if (tail) { + while (tail->next) + tail = tail->next; + tail->next = mres; + } else + tcp_dev->mr_head = mres; + mutex_unlock(&tcp_dev->mr_lock); + + return 0; +} + +static int __register_tcp_dev_notifier(struct tcpc_device *tcp_dev, + struct notifier_block *nb, uint8_t idx) +{ + struct tcp_notifier_block_wrapper *nb_wrapper; + int retval; + + nb_wrapper = devm_kzalloc( + &tcp_dev->dev, sizeof(*nb_wrapper), GFP_KERNEL); + if (!nb_wrapper) + return -ENOMEM; + nb_wrapper->action_nb = nb; + nb_wrapper->stub_nb.notifier_call = tcp_notifier_func_stub; + retval = srcu_notifier_chain_register( + tcp_dev->evt_nh + idx, &nb_wrapper->stub_nb); + if (retval < 0) { + devm_kfree(&tcp_dev->dev, nb_wrapper); + return retval; + } + retval = __add_wrapper_to_managed_res_list( + tcp_dev, nb_wrapper, nb, idx); + if (retval < 0) + dev_warn(&tcp_dev->dev, + "Failed to add resource to manager(%d)\n", retval); + + return 0; +} + +static bool __is_mulit_bits_set(uint32_t flags) +{ + if (flags) { + flags &= (flags - 1); + return flags ? true : false; + } + + return false; +} + +int register_tcp_dev_notifier(struct tcpc_device *tcp_dev, + struct notifier_block *nb, uint8_t flags) +{ + int ret = 0, i = 0; + + if (__is_mulit_bits_set(flags)) { + for (i = 0; i < TCP_NOTIFY_IDX_NR; i++) { + if (flags & (1 << i)) { + ret = __register_tcp_dev_notifier( + tcp_dev, nb, i); + if (ret < 0) + return ret; + } + } + } else { /* single bit */ + for (i = 0; i < TCP_NOTIFY_IDX_NR; i++) { + if (flags & (1 << i)) { + ret = srcu_notifier_chain_register( + &tcp_dev->evt_nh[i], nb); + break; + } + } + } + +#ifndef CONFIG_TCPC_NOTIFIER_LATE_SYNC + if (tcp_dev->desc.notifier_supply_num == 0) { + pr_info("%s already started\n", __func__); + return 0; + } + + tcp_dev->desc.notifier_supply_num--; + pr_info("%s supply_num = %d\n", __func__, + tcp_dev->desc.notifier_supply_num); + + if (tcp_dev->desc.notifier_supply_num == 0) { + cancel_delayed_work(&tcp_dev->init_work); + tcpc_device_irq_enable(tcp_dev); + } +#endif + return ret; +} +EXPORT_SYMBOL(register_tcp_dev_notifier); + + +static void *__remove_wrapper_from_managed_res_list( + struct tcpc_device *tcp_dev, void *key, int prv_id) +{ + void *retval = NULL; + struct tcpc_managed_res *mres = tcp_dev->mr_head; + struct tcpc_managed_res *prev = NULL; + + mutex_lock(&tcp_dev->mr_lock); + if (mres) { + while (mres) { + if (mres->key == key && mres->prv_id == prv_id) { + retval = mres->res; + if (prev) + prev->next = mres->next; + else + tcp_dev->mr_head = NULL; + devm_kfree(&tcp_dev->dev, mres); + break; + } + prev = mres; + mres = mres->next; + } + } + mutex_unlock(&tcp_dev->mr_lock); + + return retval; +} + +static int __unregister_tcp_dev_notifier(struct tcpc_device *tcp_dev, + struct notifier_block *nb, uint8_t idx) +{ + struct tcp_notifier_block_wrapper *nb_wrapper; + int retval; + + nb_wrapper = __remove_wrapper_from_managed_res_list(tcp_dev, nb, idx); + if (nb_wrapper) { + retval = srcu_notifier_chain_unregister( + tcp_dev->evt_nh + idx, &nb_wrapper->stub_nb); + devm_kfree(&tcp_dev->dev, nb_wrapper); + return retval; + } + + return -ENOENT; +} + +int unregister_tcp_dev_notifier(struct tcpc_device *tcp_dev, + struct notifier_block *nb, uint8_t flags) +{ + int i = 0, ret = 0; + + for (i = 0; i < TCP_NOTIFY_IDX_NR; i++) { + if (flags & (1 << i)) { + ret = __unregister_tcp_dev_notifier(tcp_dev, nb, i); + if (ret == -ENOENT) + ret = srcu_notifier_chain_unregister( + tcp_dev->evt_nh + i, nb); + if (ret < 0) + return ret; + } + } + return ret; +} +EXPORT_SYMBOL(unregister_tcp_dev_notifier); + + +void tcpc_device_unregister(struct device *dev, struct tcpc_device *tcpc) +{ + if (!tcpc) + return; + + tcpc_typec_deinit(tcpc); + +#ifdef CONFIG_USB_PD_REV30 + wakeup_source_unregister(tcpc->pd_port.pps_request_wake_lock); +#endif /* CONFIG_USB_PD_REV30 */ + wakeup_source_unregister(tcpc->detach_wake_lock); + wakeup_source_unregister(tcpc->attach_wake_lock); + +#ifdef CONFIG_DUAL_ROLE_USB_INTF + devm_dual_role_instance_unregister(&tcpc->dev, tcpc->dr_usb); +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ + + device_unregister(&tcpc->dev); + +} +EXPORT_SYMBOL(tcpc_device_unregister); + +void *tcpc_get_dev_data(struct tcpc_device *tcpc) +{ + return tcpc->drv_data; +} +EXPORT_SYMBOL(tcpc_get_dev_data); + +void tcpci_lock_typec(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->typec_lock); +} +EXPORT_SYMBOL(tcpci_lock_typec); + +void tcpci_unlock_typec(struct tcpc_device *tcpc) +{ + mutex_unlock(&tcpc->typec_lock); +} +EXPORT_SYMBOL(tcpci_unlock_typec); + +static void tcpc_init_attrs(struct device_type *dev_type) +{ + int i; + + dev_type->groups = tcpc_attr_groups; + for (i = 0; i < ARRAY_SIZE(tcpc_device_attributes); i++) + __tcpc_attrs[i] = &tcpc_device_attributes[i].attr; +} + +static int __init tcpc_class_init(void) +{ + pr_info("%s (%s)\n", __func__, TCPC_CORE_VERSION); + +#ifdef CONFIG_USB_POWER_DELIVERY + dpm_check_supported_modes(); +#endif /* CONFIG_USB_POWER_DELIVERY */ + + tcpc_class = class_create(THIS_MODULE, "tcpc"); + if (IS_ERR(tcpc_class)) { + pr_info("Unable to create tcpc class; errno = %ld\n", + PTR_ERR(tcpc_class)); + return PTR_ERR(tcpc_class); + } + tcpc_init_attrs(&tcpc_dev_type); + + pd_dbg_info_init(); + regmap_plat_init(); + + pr_info("TCPC class init OK\n"); + return 0; +} + +static void __exit tcpc_class_exit(void) +{ + regmap_plat_exit(); + pd_dbg_info_exit(); + + class_destroy(tcpc_class); + pr_info("TCPC class un-init OK\n"); +} + +subsys_initcall(tcpc_class_init); +module_exit(tcpc_class_exit); + +MODULE_DESCRIPTION("Richtek TypeC Port Control Core"); +MODULE_AUTHOR("Jeff Chang "); +MODULE_VERSION(TCPC_CORE_VERSION); +MODULE_LICENSE("GPL"); + +/* Release Version + * 2.0.14_G + * (1) Move out typec_port registration and operation to rt_pd_manager.c + * (2) Rename CONFIG_TYPEC_WAIT_BC12 to CONFIG_USB_PD_WAIT_BC12 + * (3) Not to set power/data/vconn role repeatedly + * (4) Revise vconn highV protection + * (5) Revise tcpc timer + * (6) Decrease VBUS present threshold (VBUS_CAL) by 60mV (2LSBs) for RT171x + * (7) Replace \r\n with \n for resolving logs without newlines + * (8) Remove the member time_stamp from struct pd_msg + * (9) Remove NoResponseTimer as Sink for new PD spec + * (10) Revise responses of Reject and Not_Supported + * (11) Revise the usages of pd_traffic_control and typec_power_ctrl + * (12) Revise the usages of wait_event_*() + * (13) Add PD capability for TYPEC_ATTACHED_DBGACC_SNK + * (14) Utilize rt-regmap to reduce I2C accesses + * + * 2.0.13_G + * (1) Add TCPC flags for VCONN_SAFE5V_ONLY + * (2) Add boolean property attemp_discover_svid in dts/dtsi + * (3) Add a TCPM API for postponing Type-C role change until unattached + * (4) Update VDOs according new PD spec + * (5) Add an option for enabling/disabling the support of DebugAccessory.SRC + * (6) Add the workaround for delayed ps_change related to PS_RDY + * during PR_SWAP + * (7) Always Back to PE ready state in pd_dpm_dfp_inform_id() and + * pd_dpm_dfp_inform_svids() + * (8) Re-fetch triggered_timer and enable_mask after lock acquisition + * (9) Leave low power mode only when CC is detached + * (10) Revise code related to pd_check_rev30() + * (11) Bypass BC1.2 for PR_SWAP from Source to Sink (MTK only) + * (12) Support charging icon for AudioAccessory (MTK only) + * (13) Replace tcpc_dev with tcpc + * (14) TCPCI Alert V10 and V20 co-exist + * (15) Resolve DP Source/Sink Both Connected when acting as DFP_U + * (16) Change CONFIG_TYPEC_SNK_CURR_DFT from 150 to 100 (mA) + * (17) Define CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY by default + * (18) Add an option for TCPC log with port name + * (19) USB-C states go from ErrorRecovery to Unattached.SRC with Try.SRC role + * (20) Revise dts/dtsi value for DisplayPort Alternative Mode + * (21) Mask vSafe0V IRQ before entering low power mode + * (22) Disable auto idle mode before entering low power mode + * (23) Reset Protocol FSM and clear RX alerts twice before clock gating + * + * 2.0.12_G + * (1) Fix voltage/current steps of RDO for APDO + * (2) Non-blocking TCPC notification by default + * (3) Fix synchronization/locking problems + * (4) Fix NoRp.SRC support + * + * 2.0.11_G + * (1) Fix PD compliance failures of Ellisys and MQP + * (2) Wait the result of BC1.2 before starting PD policy engine + * (3) Fix compile warnings + * (4) Fix NoRp.SRC support + * + * 2.0.10_G + * (1) fix battery noitifier plug out cause recursive locking detected in + * nh->srcu. + * + * 2.0.9_G + * (1) fix 10k A-to-C legacy cable workaround side effect when + * cable plug in at worakround flow. + * + * 2.0.8_G + * (1) fix timeout thread flow for wakeup pd event thread + * after disable timer first. + * + * 2.0.7_G + * (1) add extract pd source capability pdo defined in + * PD30 v1.1 ECN for pe40 get apdo profile. + * + * 2.0.6_G + * (1) register battery notifier for battery plug out + * avoid TA hardreset 3 times will show charing icon. + * + * 2.0.5_G + * (1) add CONFIG_TYPEC_CAP_NORP_SRC to support + * A-to-C No-Rp cable. + * (2) add handler pd eint with eint mask + * + * 2.0.4_G + * (1) add CONFIG_TCPC_NOTIFIER_LATE_SYNC to + * move irq_enable to late_initcall_sync stage + * to prevent from notifier_supply_num setting wrong. + * + * 2.0.3_G + * (1) use local_irq_XXX to instead raw_local_irq_XXX + * to fix lock prov WARNING + * (2) Remove unnecessary charger detect flow. it does + * not need to switch BC1-2 path on otg_en + * + * 2.0.2_G + * (1) Fix Coverity and check patch issue + * (2) Fix 32-bit project build error + * + * 2.0.1_G + * First released PD3.0 Driver + */ diff --git a/drivers/usb/typec/mmi_tcpc/tcpci_dual_role.c b/drivers/usb/typec/mmi_tcpc/tcpci_dual_role.c new file mode 100644 index 000000000000..4005533592e3 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpci_dual_role.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * TCPC Interface for dual role + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include "inc/tcpci.h" +#include "inc/tcpci_typec.h" + +#ifdef CONFIG_DUAL_ROLE_USB_INTF +static enum dual_role_property tcpc_dual_role_props[] = { + DUAL_ROLE_PROP_SUPPORTED_MODES, + DUAL_ROLE_PROP_MODE, + DUAL_ROLE_PROP_PR, + DUAL_ROLE_PROP_DR, + DUAL_ROLE_PROP_VCONN_SUPPLY, +}; + +static int tcpc_dual_role_get_prop(struct dual_role_phy_instance *dual_role, + enum dual_role_property prop, unsigned int *val) +{ + struct tcpc_device *tcpc = dev_get_drvdata(dual_role->dev.parent); + int ret = 0; + + switch (prop) { + case DUAL_ROLE_PROP_SUPPORTED_MODES: + *val = tcpc->dual_role_supported_modes; + break; + case DUAL_ROLE_PROP_MODE: + *val = tcpc->dual_role_mode; + break; + case DUAL_ROLE_PROP_PR: + *val = tcpc->dual_role_pr; + break; + case DUAL_ROLE_PROP_DR: + *val = tcpc->dual_role_dr; + break; + case DUAL_ROLE_PROP_VCONN_SUPPLY: + *val = tcpc->dual_role_vconn; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int tcpc_dual_role_prop_is_writeable( + struct dual_role_phy_instance *dual_role, enum dual_role_property prop) +{ + int retval = -EINVAL; + struct tcpc_device *tcpc = dev_get_drvdata(dual_role->dev.parent); + + switch (prop) { +#ifdef CONFIG_USB_POWER_DELIVERY + case DUAL_ROLE_PROP_PR: + case DUAL_ROLE_PROP_DR: + case DUAL_ROLE_PROP_VCONN_SUPPLY: +#else + case DUAL_ROLE_PROP_MODE: +#endif /* CONFIG_USB_POWER_DELIVERY */ + if (tcpc->dual_role_supported_modes == + DUAL_ROLE_SUPPORTED_MODES_DFP_AND_UFP) + retval = 1; + break; + default: + break; + } + return retval; +} + +#ifdef CONFIG_USB_POWER_DELIVERY + +static int tcpc_dual_role_set_prop_pr( + struct tcpc_device *tcpc, unsigned int val) +{ + int ret; + uint8_t role; + + switch (val) { + case DUAL_ROLE_PROP_PR_SRC: + role = PD_ROLE_SOURCE; + break; + case DUAL_ROLE_PROP_PR_SNK: + role = PD_ROLE_SINK; + break; + default: + return 0; + } + + if (val == tcpc->dual_role_pr) { + pr_info("%s wrong role (%d->%d)\n", + __func__, tcpc->dual_role_pr, val); + return 0; + } + + ret = tcpm_dpm_pd_power_swap(tcpc, role, NULL); + pr_info("%s power role swap (%d->%d): %d\n", + __func__, tcpc->dual_role_pr, val, ret); + + if (ret == TCPM_ERROR_NO_PD_CONNECTED) { + ret = tcpm_typec_role_swap(tcpc); + pr_info("%s typec role swap (%d->%d): %d\n", + __func__, tcpc->dual_role_pr, val, ret); + } + + return ret; +} + +static int tcpc_dual_role_set_prop_dr( + struct tcpc_device *tcpc, unsigned int val) +{ + int ret; + uint8_t role; + + switch (val) { + case DUAL_ROLE_PROP_DR_HOST: + role = PD_ROLE_DFP; + break; + case DUAL_ROLE_PROP_DR_DEVICE: + role = PD_ROLE_UFP; + break; + default: + return 0; + } + + if (val == tcpc->dual_role_dr) { + pr_info("%s wrong role (%d->%d)\n", + __func__, tcpc->dual_role_dr, val); + return 0; + } + + ret = tcpm_dpm_pd_data_swap(tcpc, role, NULL); + pr_info("%s data role swap (%d->%d): %d\n", + __func__, tcpc->dual_role_dr, val, ret); + + return ret; +} + +static int tcpc_dual_role_set_prop_vconn( + struct tcpc_device *tcpc, unsigned int val) +{ + int ret; + uint8_t role; + + switch (val) { + case DUAL_ROLE_PROP_VCONN_SUPPLY_NO: + role = PD_ROLE_VCONN_OFF; + break; + case DUAL_ROLE_PROP_VCONN_SUPPLY_YES: + role = PD_ROLE_VCONN_ON; + break; + default: + return 0; + } + + if (val == tcpc->dual_role_vconn) { + pr_info("%s wrong role (%d->%d)\n", + __func__, tcpc->dual_role_vconn, val); + return 0; + } + + ret = tcpm_dpm_pd_vconn_swap(tcpc, role, NULL); + pr_info("%s vconn swap (%d->%d): %d\n", + __func__, tcpc->dual_role_vconn, val, ret); + + return ret; +} + +#else /* TypeC Only */ + +static int tcpc_dual_role_set_prop_mode( + struct tcpc_device *tcpc, unsigned int val) +{ + int ret; + + if (val == tcpc->dual_role_mode) { + pr_info("%s wrong role (%d->%d)\n", + __func__, tcpc->dual_role_mode, val); + return 0; + } + + ret = tcpm_typec_role_swap(tcpc); + pr_info("%s typec role swap (%d->%d): %d\n", + __func__, tcpc->dual_role_mode, val, ret); + + return ret; +} + +#endif /* CONFIG_USB_POWER_DELIVERY */ + +static int tcpc_dual_role_set_prop(struct dual_role_phy_instance *dual_role, + enum dual_role_property prop, const unsigned int *val) +{ + struct tcpc_device *tcpc = dev_get_drvdata(dual_role->dev.parent); + + switch (prop) { +#ifdef CONFIG_USB_POWER_DELIVERY + case DUAL_ROLE_PROP_PR: + tcpc_dual_role_set_prop_pr(tcpc, *val); + break; + case DUAL_ROLE_PROP_DR: + tcpc_dual_role_set_prop_dr(tcpc, *val); + break; + case DUAL_ROLE_PROP_VCONN_SUPPLY: + tcpc_dual_role_set_prop_vconn(tcpc, *val); + break; +#else /* TypeC Only */ + case DUAL_ROLE_PROP_MODE: + tcpc_dual_role_set_prop_mode(tcpc, *val); + break; +#endif /* CONFIG_USB_POWER_DELIVERY */ + + default: + break; + } + + return 0; +} + +static void tcpc_get_dual_desc(struct tcpc_device *tcpc) +{ + struct device_node *np = of_find_node_by_name(NULL, tcpc->desc.name); + u32 val; + + if (!np) + return; + + if (of_property_read_u32(np, "tcpc-dual,supported_modes", &val) >= 0) { + if (val > DUAL_ROLE_PROP_SUPPORTED_MODES_TOTAL) + tcpc->dual_role_supported_modes = + DUAL_ROLE_SUPPORTED_MODES_DFP_AND_UFP; + else + tcpc->dual_role_supported_modes = val; + } +} + +int tcpc_dual_role_phy_init( + struct tcpc_device *tcpc) +{ + struct dual_role_phy_desc *dual_desc; + int len; + char *str_name; + + tcpc->dr_usb = devm_kzalloc(&tcpc->dev, + sizeof(*tcpc->dr_usb), GFP_KERNEL); + + dual_desc = devm_kzalloc(&tcpc->dev, sizeof(*dual_desc), GFP_KERNEL); + if (!dual_desc) + return -ENOMEM; + + tcpc_get_dual_desc(tcpc); + + len = strlen(tcpc->desc.name); + str_name = devm_kzalloc(&tcpc->dev, len+11, GFP_KERNEL); + snprintf(str_name, PAGE_SIZE, "dual-role-%s", tcpc->desc.name); + dual_desc->name = str_name; + + dual_desc->properties = tcpc_dual_role_props; + dual_desc->num_properties = ARRAY_SIZE(tcpc_dual_role_props); + dual_desc->get_property = tcpc_dual_role_get_prop; + dual_desc->set_property = tcpc_dual_role_set_prop; + dual_desc->property_is_writeable = tcpc_dual_role_prop_is_writeable; + + tcpc->dr_usb = devm_dual_role_instance_register(&tcpc->dev, dual_desc); + if (IS_ERR(tcpc->dr_usb)) { + dev_err(&tcpc->dev, "tcpc fail to register dual role usb\n"); + return -EINVAL; + } + /* init dual role phy instance property */ + tcpc->dual_role_pr = DUAL_ROLE_PROP_PR_NONE; + tcpc->dual_role_dr = DUAL_ROLE_PROP_DR_NONE; + tcpc->dual_role_mode = DUAL_ROLE_PROP_MODE_NONE; + tcpc->dual_role_vconn = DUAL_ROLE_PROP_VCONN_SUPPLY_NO; + return 0; +} +#endif /* CONFIG_DUAL_ROLE_USB_INTF */ diff --git a/drivers/usb/typec/mmi_tcpc/tcpci_event.c b/drivers/usb/typec/mmi_tcpc/tcpci_event.c new file mode 100644 index 000000000000..9ac3e313c6f9 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpci_event.c @@ -0,0 +1,1345 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * TCPC Interface for event handler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "inc/tcpci_event.h" +#include "inc/tcpci_typec.h" +#include "inc/tcpci.h" +#include "inc/pd_policy_engine.h" +#include "inc/pd_dpm_core.h" + +#ifdef CONFIG_USB_PD_POSTPONE_VDM +static void postpone_vdm_event(struct tcpc_device *tcpc) +{ + /* + * Postpone VDM retry event due to the retry reason + * maybe interrupt by some PD event .... + */ + + struct pd_event *vdm_event = &tcpc->pd_vdm_event; + + if (tcpc->pd_pending_vdm_event && vdm_event->pd_msg) { + tcpc->pd_postpone_vdm_timeout = false; + tcpc_restart_timer(tcpc, PD_PE_VDM_POSTPONE); + } +} +#endif /* CONFIG_USB_PD_POSTPONE_VDM */ + +struct pd_msg *__pd_alloc_msg(struct tcpc_device *tcpc) +{ + int i; + uint8_t mask; + + for (i = 0, mask = 1; i < PD_MSG_BUF_SIZE; i++, mask <<= 1) { + if ((mask & tcpc->pd_msg_buffer_allocated) == 0) { + tcpc->pd_msg_buffer_allocated |= mask; + return tcpc->pd_msg_buffer + i; + } + } + + PD_ERR("pd_alloc_msg failed\n"); + PD_BUG_ON(true); + + return (struct pd_msg *)NULL; +} + +struct pd_msg *pd_alloc_msg(struct tcpc_device *tcpc) +{ + struct pd_msg *pd_msg = NULL; + + mutex_lock(&tcpc->access_lock); + pd_msg = __pd_alloc_msg(tcpc); + mutex_unlock(&tcpc->access_lock); + + return pd_msg; +} + +static void __pd_free_msg(struct tcpc_device *tcpc, struct pd_msg *pd_msg) +{ + int index = pd_msg - tcpc->pd_msg_buffer; + uint8_t mask = 1 << index; + + PD_BUG_ON((mask & tcpc->pd_msg_buffer_allocated) == 0); + tcpc->pd_msg_buffer_allocated &= (~mask); +} + +static void __pd_free_event( + struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + if (pd_event->pd_msg) { + __pd_free_msg(tcpc, pd_event->pd_msg); + pd_event->pd_msg = NULL; + } +} + +void pd_free_msg(struct tcpc_device *tcpc, struct pd_msg *pd_msg) +{ + mutex_lock(&tcpc->access_lock); + __pd_free_msg(tcpc, pd_msg); + mutex_unlock(&tcpc->access_lock); +} + +void pd_free_event(struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + mutex_lock(&tcpc->access_lock); + __pd_free_event(tcpc, pd_event); + mutex_unlock(&tcpc->access_lock); +} + +/*----------------------------------------------------------------------------*/ + +static bool __pd_get_event( + struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + int index = 0; + + if (tcpc->pd_event_count <= 0) + return false; + + tcpc->pd_event_count--; + + *pd_event = + tcpc->pd_event_ring_buffer[tcpc->pd_event_head_index]; + + if (tcpc->pd_event_count) { + index = tcpc->pd_event_head_index + 1; + index %= PD_EVENT_BUF_SIZE; + } + tcpc->pd_event_head_index = index; + return true; +} + +bool pd_get_event(struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + bool ret; + + mutex_lock(&tcpc->access_lock); + ret = __pd_get_event(tcpc, pd_event); + mutex_unlock(&tcpc->access_lock); + return ret; +} + +static bool __pd_put_event(struct tcpc_device *tcpc, + const struct pd_event *pd_event, bool from_port_partner) +{ + int index; + +#ifdef CONFIG_USB_PD_POSTPONE_OTHER_VDM + if (from_port_partner) + postpone_vdm_event(tcpc); +#endif /* CONFIG_USB_PD_POSTPONE_OTHER_VDM */ + + if (tcpc->pd_event_count >= PD_EVENT_BUF_SIZE) { + PD_ERR("pd_put_event failed\n"); + return false; + } + + index = (tcpc->pd_event_head_index + tcpc->pd_event_count); + index %= PD_EVENT_BUF_SIZE; + + tcpc->pd_event_count++; + tcpc->pd_event_ring_buffer[index] = *pd_event; + + atomic_inc(&tcpc->pending_event); + wake_up(&tcpc->event_wait_que); + return true; +} + +bool pd_put_event(struct tcpc_device *tcpc, const struct pd_event *pd_event, + bool from_port_partner) +{ + bool ret; + + mutex_lock(&tcpc->access_lock); + ret = __pd_put_event(tcpc, pd_event, from_port_partner); + mutex_unlock(&tcpc->access_lock); + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static inline void pd_get_attention_event( + struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + struct pd_event attention_evt = { + .event_type = PD_EVT_PD_MSG, + .msg = PD_DATA_VENDOR_DEF, + .pd_msg = NULL, + }; + + *pd_event = attention_evt; + pd_event->pd_msg = __pd_alloc_msg(tcpc); + + if (pd_event->pd_msg == NULL) + return; + + tcpc->pd_pending_vdm_attention = false; + *pd_event->pd_msg = tcpc->pd_attention_vdm_msg; +} + +static inline bool pd_check_vdm_state_ready(struct pd_port *pd_port) +{ + switch (pd_port->pe_vdm_state) { + case PE_SNK_READY: + case PE_SRC_READY: +#ifdef CONFIG_USB_PD_CUSTOM_DBGACC + case PE_DBG_READY: +#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */ + return true; + + default: + return false; + } +} + +bool pd_get_vdm_event(struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + struct pd_event delay_evt = { + .event_type = PD_EVT_CTRL_MSG, + .msg = PD_CTRL_GOOD_CRC, + .pd_msg = NULL, + }; + + struct pd_event reset_evt = { + .event_type = PD_EVT_PE_MSG, + .msg = PD_PE_VDM_RESET, + .pd_msg = NULL, + }; + + struct pd_event discard_evt = { + .event_type = PD_EVT_HW_MSG, + .msg = PD_HW_TX_DISCARD, + .pd_msg = NULL, + }; + + struct pd_event *vdm_event = &tcpc->pd_vdm_event; + + if (tcpc->pd_pending_vdm_discard) { + mutex_lock(&tcpc->access_lock); + *pd_event = discard_evt; + tcpc->pd_pending_vdm_discard = false; + mutex_unlock(&tcpc->access_lock); + return true; + } + + if (tcpc->pd_pending_vdm_event) { + if (vdm_event->pd_msg && !tcpc->pd_postpone_vdm_timeout) + return false; + + mutex_lock(&tcpc->access_lock); + if (tcpc->pd_pending_vdm_good_crc) { + *pd_event = delay_evt; + tcpc->pd_pending_vdm_good_crc = false; + } else if (tcpc->pd_pending_vdm_reset) { + *pd_event = reset_evt; + tcpc->pd_pending_vdm_reset = false; + } else { + *pd_event = *vdm_event; + tcpc->pd_pending_vdm_event = false; + } + + mutex_unlock(&tcpc->access_lock); + return true; + } + + if (tcpc->pd_pending_vdm_attention + && pd_check_vdm_state_ready(&tcpc->pd_port)) { + mutex_lock(&tcpc->access_lock); + pd_get_attention_event(tcpc, pd_event); + mutex_unlock(&tcpc->access_lock); + return true; + } + + return false; +} + +static inline bool reset_pe_vdm_state( + struct tcpc_device *tcpc, uint32_t vdm_hdr) +{ + bool vdm_reset = false; + struct pd_port *pd_port = &tcpc->pd_port; + + if (PD_VDO_SVDM(vdm_hdr)) { + if (PD_VDO_CMDT(vdm_hdr) == CMDT_INIT) + vdm_reset = true; + } else { + if (pd_port->data_role == PD_ROLE_UFP) + vdm_reset = true; + } + + if (vdm_reset) + tcpc->pd_pending_vdm_reset = true; + + return vdm_reset; +} + +static inline bool pd_is_init_attention_event( + struct tcpc_device *tcpc, struct pd_event *pd_event) +{ + uint32_t vdm_hdr = pd_event->pd_msg->payload[0]; + + if (!PD_VDO_SVDM(vdm_hdr)) + return false; + + if ((PD_VDO_CMDT(vdm_hdr) == CMDT_INIT) && + PD_VDO_CMD(vdm_hdr) == CMD_ATTENTION) { + return true; + } + + return false; +} + +bool pd_put_vdm_event(struct tcpc_device *tcpc, + struct pd_event *pd_event, bool from_port_partner) +{ + bool ignore_evt = false; + struct pd_msg *pd_msg = pd_event->pd_msg; + + mutex_lock(&tcpc->access_lock); + + if (from_port_partner && + pd_is_init_attention_event(tcpc, pd_event)) { + TCPC_DBG("AttEvt\n"); + ignore_evt = true; + tcpc->pd_pending_vdm_attention = true; + tcpc->pd_attention_vdm_msg = *pd_msg; + + atomic_inc(&tcpc->pending_event); + wake_up(&tcpc->event_wait_que); + } + + if (tcpc->pd_pending_vdm_event && (!ignore_evt)) { + /* If message from port partner, we have to overwrite it */ + /* If message from TCPM, we will reset_vdm later */ + ignore_evt = !from_port_partner; + + if (from_port_partner) { + if (pd_event_ctrl_msg_match( + &tcpc->pd_vdm_event, + PD_CTRL_GOOD_CRC)) { + TCPC_DBG2("PostponeVDM GoodCRC\n"); + tcpc->pd_pending_vdm_good_crc = true; + } + + __pd_free_event(tcpc, &tcpc->pd_vdm_event); + } + } + + if (ignore_evt) { + __pd_free_event(tcpc, pd_event); + mutex_unlock(&tcpc->access_lock); + return false; + } + + tcpc->pd_vdm_event = *pd_event; + tcpc->pd_pending_vdm_event = true; + tcpc->pd_postpone_vdm_timeout = true; + + if (from_port_partner) { + + PD_BUG_ON(pd_msg == NULL); + tcpc->pd_last_vdm_msg = *pd_msg; + reset_pe_vdm_state(tcpc, pd_msg->payload[0]); + +#ifdef CONFIG_USB_PD_POSTPONE_FIRST_VDM + postpone_vdm_event(tcpc); + mutex_unlock(&tcpc->access_lock); + return true; +#endif /* CONFIG_USB_PD_POSTPONE_FIRST_VDM */ + } + + atomic_inc(&tcpc->pending_event); + wake_up(&tcpc->event_wait_que); + mutex_unlock(&tcpc->access_lock); + + return true; +} + +bool pd_put_last_vdm_event(struct tcpc_device *tcpc) +{ + struct pd_msg *pd_msg = &tcpc->pd_last_vdm_msg; + struct pd_event *vdm_event = &tcpc->pd_vdm_event; + + mutex_lock(&tcpc->access_lock); + + tcpc->pd_pending_vdm_discard = true; + atomic_inc(&tcpc->pending_event); + wake_up(&tcpc->event_wait_que); + + /* If the last VDM event isn't INIT event, don't put it again */ + if (!reset_pe_vdm_state(tcpc, pd_msg->payload[0])) { + mutex_unlock(&tcpc->access_lock); + return true; + } + + vdm_event->event_type = PD_EVT_HW_MSG; + vdm_event->msg = PD_HW_RETRY_VDM; + + if (tcpc->pd_pending_vdm_event) + __pd_free_event(tcpc, &tcpc->pd_vdm_event); + + vdm_event->pd_msg = __pd_alloc_msg(tcpc); + + if (vdm_event->pd_msg == NULL) { + mutex_unlock(&tcpc->access_lock); + return false; + } + + *vdm_event->pd_msg = *pd_msg; + tcpc->pd_pending_vdm_event = true; + tcpc->pd_postpone_vdm_timeout = true; + +#ifdef CONFIG_USB_PD_POSTPONE_RETRY_VDM + postpone_vdm_event(tcpc); +#endif /* CONFIG_USB_PD_POSTPONE_RETRY_VDM */ + + mutex_unlock(&tcpc->access_lock); + return true; +} + +/*----------------------------------------------------------------------------*/ + +static bool __pd_get_deferred_tcp_event( + struct tcpc_device *tcpc, struct tcp_dpm_event *tcp_event) +{ + int index = 0; + + if (tcpc->tcp_event_count <= 0) + return false; + + tcpc->tcp_event_count--; + + *tcp_event = + tcpc->tcp_event_ring_buffer[tcpc->tcp_event_head_index]; + + if (tcpc->tcp_event_count) { + index = tcpc->tcp_event_head_index + 1; + index %= TCP_EVENT_BUF_SIZE; + } + tcpc->tcp_event_head_index = index; + return true; +} + +bool pd_get_deferred_tcp_event( + struct tcpc_device *tcpc, struct tcp_dpm_event *tcp_event) +{ + bool ret; + + mutex_lock(&tcpc->access_lock); + ret = __pd_get_deferred_tcp_event(tcpc, tcp_event); +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + if (tcpc->tcp_event_count) + tcpc_restart_timer(tcpc, PD_TIMER_DEFERRED_EVT); + else + tcpc_disable_timer(tcpc, PD_TIMER_DEFERRED_EVT); +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + mutex_unlock(&tcpc->access_lock); + + return ret; +} + +static bool __pd_put_deferred_tcp_event( + struct tcpc_device *tcpc, const struct tcp_dpm_event *tcp_event) +{ + int index; + + index = (tcpc->tcp_event_head_index + tcpc->tcp_event_count); + index %= TCP_EVENT_BUF_SIZE; + + tcpc->tcp_event_count++; + tcpc->tcp_event_ring_buffer[index] = *tcp_event; + + atomic_inc(&tcpc->pending_event); + wake_up(&tcpc->event_wait_que); + return true; +} + +bool pd_put_deferred_tcp_event( + struct tcpc_device *tcpc, const struct tcp_dpm_event *tcp_event) +{ + bool ret = true; + struct pd_port *pd_port = &tcpc->pd_port; + + mutex_lock(&pd_port->pd_lock); + mutex_lock(&tcpc->access_lock); + + if (!tcpc->pd_pe_running || tcpc->pd_wait_pe_idle) { + PD_ERR("pd_put_tcp_event failed0\n"); + ret = false; + goto unlock_out; + } + + if (tcpc->tcp_event_count >= TCP_EVENT_BUF_SIZE) { + PD_ERR("pd_put_tcp_event failed1\n"); + ret = false; + goto unlock_out; + } + + if (tcpc->pd_wait_hard_reset_complete) { + PD_ERR("pd_put_tcp_event failed2\n"); + ret = false; + goto unlock_out; + } + + switch (tcp_event->event_id) { + case TCP_DPM_EVT_DISCOVER_CABLE: + case TCP_DPM_EVT_CABLE_SOFTRESET: + dpm_reaction_set(pd_port, + DPM_REACTION_DYNAMIC_VCONN | + DPM_REACTION_VCONN_STABLE_DELAY); + break; + } + + ret = __pd_put_deferred_tcp_event(tcpc, tcp_event); +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + if (ret) + pd_port->pe_data.pd_traffic_idle = false; + if (tcpc->tcp_event_count == 1) + tcpc_enable_timer(tcpc, PD_TIMER_DEFERRED_EVT); +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + + dpm_reaction_set_ready_once(pd_port); + +unlock_out: + mutex_unlock(&tcpc->access_lock); + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + +void pd_notify_tcp_vdm_event_2nd_result(struct pd_port *pd_port, uint8_t ret) +{ +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + struct tcp_dpm_event *tcp_event = &pd_port->tcp_event; + + if (pd_port->tcp_event_id_2nd == TCP_DPM_EVT_UNKONW) + return; + + if (pd_port->tcp_event_id_2nd < TCP_DPM_EVT_VDM_COMMAND || + pd_port->tcp_event_id_2nd >= TCP_DPM_EVT_IMMEDIATELY) + return; + + if (tcp_event->event_cb != NULL) + tcp_event->event_cb(pd_port->tcpc, ret, tcp_event); + + pd_port->tcp_event_id_2nd = TCP_DPM_EVT_UNKONW; +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ +} + +void pd_notify_tcp_event_2nd_result(struct pd_port *pd_port, int ret) +{ +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + struct tcp_dpm_event *tcp_event = &pd_port->tcp_event; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->tcp_event_id_2nd == TCP_DPM_EVT_UNKONW) + return; + + switch (ret) { + case TCP_DPM_RET_DROP_SENT_SRESET: + if (pd_port->tcp_event_id_2nd == TCP_DPM_EVT_SOFTRESET + && pd_port->tcp_event_drop_reset_once) { + pd_port->tcp_event_drop_reset_once = false; + return; + } + break; + case TCP_DPM_RET_DROP_SENT_HRESET: + if (pd_port->tcp_event_id_2nd == TCP_DPM_EVT_HARD_RESET + && pd_port->tcp_event_drop_reset_once) { + pd_port->tcp_event_drop_reset_once = false; + return; + } + break; + case TCP_DPM_RET_SUCCESS: + /* Ignore VDM */ + if (pd_port->tcp_event_id_2nd >= TCP_DPM_EVT_VDM_COMMAND + && pd_port->tcp_event_id_2nd < TCP_DPM_EVT_IMMEDIATELY) + return; + + break; + } + + TCPC_DBG2("tcp_event_2nd:evt%d=%d\n", + pd_port->tcp_event_id_2nd, ret); + + if (tcp_event->event_cb != NULL) + tcp_event->event_cb(tcpc, ret, tcp_event); + + pd_port->tcp_event_id_2nd = TCP_DPM_EVT_UNKONW; +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ +} + +void pd_notify_tcp_event_1st_result(struct pd_port *pd_port, int ret) +{ + bool cb = true; + struct tcp_dpm_event *tcp_event = &pd_port->tcp_event; + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (pd_port->tcp_event_id_1st == TCP_DPM_EVT_UNKONW) + return; + + TCPC_DBG2("tcp_event_1st:evt%d=%d\n", + pd_port->tcp_event_id_1st, ret); + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + if (ret == TCP_DPM_RET_SENT) { + cb = false; + pd_port->tcp_event_id_2nd = tcp_event->event_id; + } +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + + if (cb && tcp_event->event_cb != NULL) + tcp_event->event_cb(tcpc, ret, tcp_event); + + pd_port->tcp_event_id_1st = TCP_DPM_EVT_UNKONW; +} + +static void __tcp_event_buf_reset( + struct tcpc_device *tcpc, uint8_t reason) +{ + struct tcp_dpm_event tcp_event; + + pd_notify_tcp_event_2nd_result(&tcpc->pd_port, reason); + + while (__pd_get_deferred_tcp_event(tcpc, &tcp_event)) { + if (tcp_event.event_cb != NULL) + tcp_event.event_cb(tcpc, reason, &tcp_event); + } +} + +void pd_notify_tcp_event_buf_reset(struct pd_port *pd_port, uint8_t reason) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + pd_notify_tcp_event_1st_result(pd_port, reason); + + mutex_lock(&tcpc->access_lock); + __tcp_event_buf_reset(tcpc, reason); + mutex_unlock(&tcpc->access_lock); +} + +/*----------------------------------------------------------------------------*/ + +static void __pd_event_buf_reset(struct tcpc_device *tcpc, uint8_t reason) +{ + struct pd_event pd_event; + + tcpc->pd_hard_reset_event_pending = false; + while (__pd_get_event(tcpc, &pd_event)) + __pd_free_event(tcpc, &pd_event); + + if (tcpc->pd_pending_vdm_event) { + __pd_free_event(tcpc, &tcpc->pd_vdm_event); + tcpc->pd_pending_vdm_event = false; + } + + tcpc->pd_pending_vdm_reset = false; + tcpc->pd_pending_vdm_good_crc = false; + tcpc->pd_pending_vdm_attention = false; + tcpc->pd_pending_vdm_discard = false; + + __tcp_event_buf_reset(tcpc, reason); + /* PD_BUG_ON(tcpc->pd_msg_buffer_allocated != 0); */ +} + +void pd_event_buf_reset(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->access_lock); + __pd_event_buf_reset(tcpc, TCP_DPM_RET_DROP_CC_DETACH); + mutex_unlock(&tcpc->access_lock); +} + +/*----------------------------------------------------------------------------*/ + +static inline bool __pd_put_hw_event( + struct tcpc_device *tcpc, uint8_t hw_event) +{ + struct pd_event evt = { + .event_type = PD_EVT_HW_MSG, + .msg = hw_event, + .pd_msg = NULL, + }; + + return __pd_put_event(tcpc, &evt, false); +} + +static inline bool __pd_put_pe_event( + struct tcpc_device *tcpc, uint8_t pe_event) +{ + struct pd_event evt = { + .event_type = PD_EVT_PE_MSG, + .msg = pe_event, + .pd_msg = NULL, + }; + + return __pd_put_event(tcpc, &evt, false); +} + +bool __pd_put_cc_attached_event( + struct tcpc_device *tcpc, uint8_t type) +{ + struct pd_event evt = { + .event_type = PD_EVT_HW_MSG, + .msg = PD_HW_CC_ATTACHED, + .msg_sec = type, + .pd_msg = NULL, + }; + + switch (type) { + case TYPEC_ATTACHED_SNK: + case TYPEC_ATTACHED_SRC: + case TYPEC_ATTACHED_DBGACC_SNK: + tcpc->pd_pe_running = true; + tcpc->pd_wait_pe_idle = false; + break; + default: + break; + } + + return __pd_put_event(tcpc, &evt, false); +} + +bool pd_put_cc_attached_event( + struct tcpc_device *tcpc, uint8_t type) +{ + bool ret = false; +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_USB_PD_WAIT_BC12 + int rv = 0; + union power_supply_propval val = {.intval = 0}; +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + + mutex_lock(&tcpc->access_lock); + +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_USB_PD_WAIT_BC12 + rv = power_supply_get_property(tcpc->usb_psy, + POWER_SUPPLY_PROP_REAL_TYPE, &val); + if ((type == TYPEC_ATTACHED_SNK || type == TYPEC_ATTACHED_DBGACC_SNK) && + (rv < 0 || val.intval == POWER_SUPPLY_TYPE_UNKNOWN)) { + tcpc->pd_wait_bc12_count = 1; + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_PD_WAIT_BC12); + mutex_unlock(&tcpc->access_lock); + return ret; + } + tcpc->pd_wait_bc12_count = 0; + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_PD_WAIT_BC12); +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + + ret = __pd_put_cc_attached_event(tcpc, type); + + mutex_unlock(&tcpc->access_lock); + + return ret; +} + +void pd_put_cc_detached_event(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->access_lock); + +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_USB_PD_WAIT_BC12 + tcpc->pd_wait_bc12_count = 0; + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_PD_WAIT_BC12); +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + + tcpci_notify_hard_reset_state( + tcpc, TCP_HRESET_RESULT_FAIL); + + __pd_event_buf_reset(tcpc, TCP_DPM_RET_DROP_CC_DETACH); + __pd_put_hw_event(tcpc, PD_HW_CC_DETACHED); + + tcpc->pd_wait_pe_idle = true; + tcpc->pd_pe_running = false; + tcpc->pd_wait_pr_swap_complete = false; + tcpc->pd_hard_reset_event_pending = false; + tcpc->pd_wait_vbus_once = PD_WAIT_VBUS_DISABLE; + tcpc->pd_bist_mode = PD_BIST_MODE_DISABLE; + tcpc->pd_ping_event_pending = false; + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE + tcpc->pd_during_direct_charge = false; +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + tcpc->pd_discard_pending = false; +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + + mutex_unlock(&tcpc->access_lock); +} + +void pd_put_recv_hard_reset_event(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->access_lock); + + tcpci_notify_hard_reset_state( + tcpc, TCP_HRESET_SIGNAL_RECV); + + tcpc->pd_transmit_state = PD_TX_STATE_HARD_RESET; + + if ((!tcpc->pd_hard_reset_event_pending) && + (!tcpc->pd_wait_pe_idle) && + tcpc->pd_pe_running) { + __pd_event_buf_reset(tcpc, TCP_DPM_RET_DROP_RECV_HRESET); + __pd_put_hw_event(tcpc, PD_HW_RECV_HARD_RESET); + tcpc->pd_bist_mode = PD_BIST_MODE_DISABLE; + tcpc->pd_hard_reset_event_pending = true; + tcpc->pd_ping_event_pending = false; + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE + tcpc->pd_during_direct_charge = false; +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + } + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + tcpc->pd_discard_pending = false; +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + + mutex_unlock(&tcpc->access_lock); +} + +void pd_put_sent_hard_reset_event(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->access_lock); + + if (tcpc->pd_wait_hard_reset_complete) + __pd_event_buf_reset(tcpc, TCP_DPM_RET_DROP_SENT_HRESET); + else + TCPC_DBG2("[HReset] Unattached\n"); + + tcpc->pd_transmit_state = PD_TX_STATE_GOOD_CRC; + __pd_put_pe_event(tcpc, PD_PE_HARD_RESET_COMPLETED); + + mutex_unlock(&tcpc->access_lock); +} + +bool pd_put_pd_msg_event(struct tcpc_device *tcpc, struct pd_msg *pd_msg) +{ + uint32_t cnt, cmd, extend; + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + bool discard_pending = false; +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + + struct pd_event evt = { + .event_type = PD_EVT_PD_MSG, + .pd_msg = pd_msg, + }; + + cnt = PD_HEADER_CNT(pd_msg->msg_hdr); + cmd = PD_HEADER_TYPE(pd_msg->msg_hdr); + extend = PD_HEADER_EXT(pd_msg->msg_hdr); + + /* bist mode */ + mutex_lock(&tcpc->access_lock); + if (tcpc->pd_bist_mode != PD_BIST_MODE_DISABLE) { + TCPC_DBG2("BIST_MODE_RX\n"); + __pd_free_event(tcpc, &evt); + mutex_unlock(&tcpc->access_lock); + return 0; + } + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + if (tcpc->pd_discard_pending && + (pd_msg->frame_type == TCPC_TX_SOP) && + (tcpc->tcpc_flags & TCPC_FLAGS_RETRY_CRC_DISCARD)) { + + discard_pending = true; + tcpc->pd_discard_pending = false; + + if ((cmd == PD_CTRL_GOOD_CRC) && (cnt == 0)) { + TCPC_DBG2("RETRANSMIT\n"); + __pd_free_event(tcpc, &evt); + mutex_unlock(&tcpc->access_lock); + + /* TODO: check it later */ + tcpc_disable_timer(tcpc, PD_TIMER_DISCARD); + tcpci_retransmit(tcpc); + return 0; + } + } +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + +#ifdef CONFIG_USB_PD_DROP_REPEAT_PING + if (cnt == 0 && cmd == PD_CTRL_PING) { + /* reset ping_test_mode only if cc_detached */ + if (!tcpc->pd_ping_event_pending) { + TCPC_INFO("ping_test_mode\n"); + tcpc->pd_ping_event_pending = true; + tcpci_set_bist_test_mode(tcpc, true); + } else { + __pd_free_event(tcpc, &evt); + mutex_unlock(&tcpc->access_lock); + return 0; + } + } +#endif /* CONFIG_USB_PD_DROP_REPEAT_PING */ + + if (cnt != 0 && cmd == PD_DATA_BIST && extend == 0) + tcpc->pd_bist_mode = PD_BIST_MODE_EVENT_PENDING; + + mutex_unlock(&tcpc->access_lock); + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + if (discard_pending) { + tcpc_disable_timer(tcpc, PD_TIMER_DISCARD); + pd_put_hw_event(tcpc, PD_HW_TX_FAILED); + } +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + + if (cnt != 0 && cmd == PD_DATA_VENDOR_DEF) + return pd_put_vdm_event(tcpc, &evt, true); + + if (!pd_put_event(tcpc, &evt, true)) { + pd_free_event(tcpc, &evt); + return false; + } + + return true; +} + +static void pd_report_vbus_present(struct tcpc_device *tcpc) +{ + tcpc->pd_wait_vbus_once = PD_WAIT_VBUS_DISABLE; + __pd_put_hw_event(tcpc, PD_HW_VBUS_PRESENT); +} + +void pd_put_vbus_changed_event(struct tcpc_device *tcpc, bool from_ic) +{ + int vbus_valid; + bool postpone_vbus_present = false; + + mutex_lock(&tcpc->access_lock); + vbus_valid = tcpci_check_vbus_valid(tcpc); + + switch (tcpc->pd_wait_vbus_once) { + case PD_WAIT_VBUS_VALID_ONCE: + if (vbus_valid) { +#if CONFIG_USB_PD_VBUS_PRESENT_TOUT + postpone_vbus_present = from_ic; +#endif /* CONFIG_USB_PD_VBUS_PRESENT_TOUT */ + if (!postpone_vbus_present) + pd_report_vbus_present(tcpc); + } + break; + + case PD_WAIT_VBUS_INVALID_ONCE: + if (!vbus_valid) { + tcpc->pd_wait_vbus_once = PD_WAIT_VBUS_DISABLE; + __pd_put_hw_event(tcpc, PD_HW_VBUS_ABSENT); + } + break; + } + mutex_unlock(&tcpc->access_lock); + +#if CONFIG_USB_PD_VBUS_PRESENT_TOUT + if (postpone_vbus_present) + tcpc_enable_timer(tcpc, PD_TIMER_VBUS_PRESENT); +#endif /* CONFIG_USB_PD_VBUS_PRESENT_TOUT */ +} + +void pd_put_vbus_safe0v_event(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_USB_PD_SAFE0V_TIMEOUT + tcpc_disable_timer(tcpc, PD_TIMER_VSAFE0V_TOUT); +#endif /* CONFIG_USB_PD_SAFE0V_TIMEOUT */ + + mutex_lock(&tcpc->access_lock); + if (tcpc->pd_wait_vbus_once == PD_WAIT_VBUS_SAFE0V_ONCE) { + tcpc->pd_wait_vbus_once = PD_WAIT_VBUS_DISABLE; + tcpci_enable_force_discharge(tcpc, false, 0); + __pd_put_hw_event(tcpc, PD_HW_VBUS_SAFE0V); + } + mutex_unlock(&tcpc->access_lock); +} + +void pd_put_vbus_stable_event(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->access_lock); + if (tcpc->pd_wait_vbus_once == PD_WAIT_VBUS_STABLE_ONCE) { + tcpc->pd_wait_vbus_once = PD_WAIT_VBUS_DISABLE; +#ifdef CONFIG_USB_PD_SRC_HIGHCAP_POWER + tcpci_enable_force_discharge(tcpc, false, 0); +#endif /* CONFIG_USB_PD_SRC_HIGHCAP_POWER */ + __pd_put_hw_event(tcpc, PD_HW_VBUS_STABLE); + } + mutex_unlock(&tcpc->access_lock); +} + +void pd_put_vbus_present_event(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->access_lock); + pd_report_vbus_present(tcpc); + mutex_unlock(&tcpc->access_lock); +} + +/* ---- PD Notify TCPC ---- */ + +void pd_try_put_pe_idle_event(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + if (tcpc->pd_transmit_state < PD_TX_STATE_WAIT) + __pd_put_pe_event(tcpc, PD_PE_IDLE); + mutex_unlock(&tcpc->access_lock); +} + +void pd_notify_pe_idle(struct pd_port *pd_port) +{ + bool notify_pe_idle = false; + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + if (tcpc->pd_wait_pe_idle) { + notify_pe_idle = true; + tcpc->pd_wait_pe_idle = false; + } + + tcpc->pd_pe_running = false; + mutex_unlock(&tcpc->access_lock); + + pd_update_connect_state(pd_port, PD_CONNECT_NONE); + + if (notify_pe_idle) + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_PE_IDLE); +} + +void pd_notify_pe_wait_vbus_once(struct pd_port *pd_port, int wait_evt) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + tcpc->pd_wait_vbus_once = wait_evt; + mutex_unlock(&tcpc->access_lock); + + switch (wait_evt) { + case PD_WAIT_VBUS_VALID_ONCE: + case PD_WAIT_VBUS_INVALID_ONCE: + pd_put_vbus_changed_event(tcpc, false); + break; + + case PD_WAIT_VBUS_SAFE0V_ONCE: +#ifdef CONFIG_TCPC_VSAFE0V_DETECT + if (tcpci_check_vsafe0v(tcpc, true)) { + pd_put_vbus_safe0v_event(tcpc); + break; + } +#else + pd_enable_timer(pd_port, PD_TIMER_VSAFE0V_DELAY); +#endif /* CONFIG_TCPC_VSAFE0V_DETECT */ + +#ifdef CONFIG_USB_PD_SAFE0V_TIMEOUT + pd_enable_timer(pd_port, PD_TIMER_VSAFE0V_TOUT); +#endif /* CONFIG_USB_PD_SAFE0V_TIMEOUT */ + + mutex_lock(&tcpc->access_lock); + tcpci_enable_force_discharge(tcpc, true, 0); + mutex_unlock(&tcpc->access_lock); + break; + } +} + +void pd_notify_pe_error_recovery(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + + tcpci_notify_hard_reset_state( + tcpc, TCP_HRESET_RESULT_FAIL); + + tcpc->pd_wait_pr_swap_complete = false; + __tcp_event_buf_reset(tcpc, TCP_DPM_RET_DROP_ERROR_REOCVERY); + mutex_unlock(&tcpc->access_lock); + + tcpc_typec_error_recovery(tcpc); +} + +#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER +void pd_notify_pe_over_recv_hreset(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + tcpc->pd_wait_hard_reset_complete = false; + tcpc->pd_wait_pr_swap_complete = false; + mutex_unlock(&tcpc->access_lock); + + disable_irq(chip->irq); + tcpci_lock_typec(tcpc); + tcpci_init(tcpc, true); + tcpci_set_cc(tcpc, TYPEC_CC_OPEN); + tcpci_unlock_typec(tcpc); + tcpci_set_rx_enable(tcpc, PD_RX_CAP_PE_IDLE); + tcpc_enable_timer(tcpc, TYPEC_TIMER_ERROR_RECOVERY); + enable_irq_wake(chip->irq); +} +#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */ + +void pd_notify_pe_transit_to_default(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + pd_update_connect_state(pd_port, PD_CONNECT_HARD_RESET); + + mutex_lock(&tcpc->access_lock); + tcpc->pd_hard_reset_event_pending = false; + tcpc->pd_wait_pr_swap_complete = false; + tcpc->pd_bist_mode = PD_BIST_MODE_DISABLE; + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE + tcpc->pd_during_direct_charge = false; +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + mutex_unlock(&tcpc->access_lock); +} + +void pd_notify_pe_hard_reset_completed(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + tcpci_notify_hard_reset_state( + tcpc, TCP_HRESET_RESULT_DONE); + mutex_unlock(&tcpc->access_lock); +} + +void pd_notify_pe_send_hard_reset(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + tcpc->pd_transmit_state = PD_TX_STATE_WAIT_HARD_RESET; + tcpci_notify_hard_reset_state(tcpc, TCP_HRESET_SIGNAL_SEND); + mutex_unlock(&tcpc->access_lock); +} + +void pd_notify_pe_execute_pr_swap(struct pd_port *pd_port, bool start_swap) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + pd_port->pe_data.during_swap = start_swap; + mutex_lock(&tcpc->access_lock); + tcpc->pd_wait_pr_swap_complete = true; + mutex_unlock(&tcpc->access_lock); +} + +void pd_notify_pe_cancel_pr_swap(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + if (!tcpc->pd_wait_pr_swap_complete) + return; + + pd_port->pe_data.during_swap = false; + mutex_lock(&tcpc->access_lock); + tcpc->pd_wait_pr_swap_complete = false; + mutex_unlock(&tcpc->access_lock); + + /* + * CC_Alert was ignored if pd_wait_pr_swap_complete = true + * So enable PDDebounce to detect CC_Again after cancel_pr_swap. + */ + + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); + + if (!tcpci_check_vbus_valid(tcpc) + && (pd_port->request_v >= 4000)) { + TCPC_DBG("cancel_pr_swap_vbus=0\n"); + pd_put_tcp_pd_event(pd_port, TCP_DPM_EVT_ERROR_RECOVERY); + } +} + +void pd_notify_pe_reset_protocol(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + tcpc->pd_wait_pr_swap_complete = false; + mutex_unlock(&tcpc->access_lock); +} + +void pd_noitfy_pe_bist_mode(struct pd_port *pd_port, uint8_t mode) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + tcpc->pd_bist_mode = mode; + mutex_unlock(&tcpc->access_lock); +} + +void pd_notify_pe_transmit_msg( + struct pd_port *pd_port, uint8_t type) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + mutex_lock(&tcpc->access_lock); + tcpc->pd_transmit_state = type; + mutex_unlock(&tcpc->access_lock); +} + +void pd_notify_pe_pr_changed(struct pd_port *pd_port) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + + /* Check mutex later, actually, + * typec layer will ignore all cc-change during PR-SWAP + */ + + /* mutex_lock(&tcpc->access_lock); */ + tcpc_typec_handle_pe_pr_swap(tcpc); + /* mutex_unlock(&tcpc->access_lock); */ +} + +void pd_notify_pe_snk_explicit_contract(struct pd_port *pd_port) +{ +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + struct pe_data *pe_data = &pd_port->pe_data; + struct tcpc_device *tcpc = pd_port->tcpc; + + if (pe_data->explicit_contract) + return; + + if (tcpc->typec_remote_rp_level == TYPEC_CC_VOLT_SNK_3_0) + pe_data->pd_traffic_control = PD_SINK_TX_OK; + else + pe_data->pd_traffic_control = PD_SINK_TX_NG; + +#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP + if (pe_data->pd_traffic_control == PD_SINK_TX_OK) { + pe_data->pd_traffic_control = PD_SINK_TX_START; + pd_restart_timer(pd_port, PD_TIMER_SNK_FLOW_DELAY); + } +#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */ +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +} + +void pd_notify_pe_src_explicit_contract(struct pd_port *pd_port) +{ + uint8_t pull = 0; + + struct pe_data *pe_data = &pd_port->pe_data; + struct tcpc_device *tcpc = pd_port->tcpc; + + if (pe_data->explicit_contract) { +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID +#ifdef CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP + if (pd_check_rev30(pd_port) && + (pe_data->pd_traffic_control == PD_SOURCE_TX_START)) + pd_restart_timer(pd_port, PD_TIMER_SINK_TX); +#endif /* CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP */ +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + return; + } + + if (tcpc->typec_local_rp_level == TYPEC_CC_RP_DFT) + pull = TYPEC_CC_RP_1_5; + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + if (pd_check_rev30(pd_port)) { + pull = TYPEC_CC_RP_3_0; + +#ifdef CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP + pe_data->pd_traffic_control = PD_SOURCE_TX_START; + pd_enable_timer(pd_port, PD_TIMER_SINK_TX); +#else + pe_data->pd_traffic_control = PD_SINK_TX_OK; +#endif /* CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP */ + } +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + + if (pull) { + tcpci_lock_typec(tcpc); + tcpci_set_cc(tcpc, pull); + tcpci_unlock_typec(tcpc); + } +} + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE +void pd_notify_pe_direct_charge(struct pd_port *pd_port, bool en) +{ + struct tcpc_device *tcpc = pd_port->tcpc; + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + /* TODO: check it later */ + if (pd_port->request_apdo) + en = true; +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + + mutex_lock(&tcpc->access_lock); + tcpc->pd_during_direct_charge = en; + mutex_unlock(&tcpc->access_lock); +} +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + +/* ---- init ---- */ +static int tcpc_event_thread_fn(void *data) +{ + struct tcpc_device *tcpc = data; + struct sched_param sch_param = {.sched_priority = MAX_RT_PRIO - 2}; + + /* set_user_nice(current, -20); */ + /* current->flags |= PF_NOFREEZE;*/ + + sched_setscheduler(current, SCHED_FIFO, &sch_param); + + while (true) { + wait_event(tcpc->event_wait_que, + atomic_read(&tcpc->pending_event) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + do { + atomic_dec_if_positive(&tcpc->pending_event); + } while (pd_policy_engine_run(tcpc) && !kthread_should_stop()); + } + + return 0; +} + +int tcpci_event_init(struct tcpc_device *tcpc) +{ + init_waitqueue_head(&tcpc->event_wait_que); + atomic_set(&tcpc->pending_event, 0); + tcpc->event_task = kthread_run(tcpc_event_thread_fn, tcpc, + "tcpc_event_%s", tcpc->desc.name); + + return 0; +} + +int tcpci_event_deinit(struct tcpc_device *tcpc) +{ + if (tcpc->event_task != NULL) + kthread_stop(tcpc->event_task); + + return 0; +} diff --git a/drivers/usb/typec/mmi_tcpc/tcpci_timer.c b/drivers/usb/typec/mmi_tcpc/tcpci_timer.c new file mode 100644 index 000000000000..3be867e08c09 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpci_timer.c @@ -0,0 +1,1388 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * TCPC Interface for timer handler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "inc/tcpci.h" +#include "inc/tcpci_timer.h" +#include "inc/tcpci_typec.h" + +#define RT_MASK64(i) (((uint64_t)1) << i) + +#define TIMEOUT_VAL(val) (val * 1000) +#define TIMEOUT_RANGE(min, max) ((min * 4000 + max * 1000)/5) +#define TIMEOUT_VAL_US(val) (val) + +/* Debug message Macro */ +#if TCPC_TIMER_DBG_EN +#define TCPC_TIMER_DBG(tcpc, id) \ +{ \ + RT_DBG_INFO("Trigger %s\n", tcpc_timer_name[id]); \ +} +#else +#define TCPC_TIMER_DBG(format, args...) +#endif /* TCPC_TIMER_DBG_EN */ + +#if TCPC_TIMER_INFO_EN +#define TCPC_TIMER_EN_DBG(tcpc, id) \ +{ \ + RT_DBG_INFO("Enable %s\n", tcpc_timer_name[id]); \ +} +#else +#define TCPC_TIMER_EN_DBG(format, args...) +#endif /* TCPC_TIMER_INFO_EN */ + +static inline void tcpc_clear_timer_tick(struct tcpc_device *tcpc, int nr); + +static inline uint64_t tcpc_get_timer_enable_mask(struct tcpc_device *tcpc) +{ + uint64_t data; + unsigned long flags; + + down(&tcpc->timer_enable_mask_lock); + local_irq_save(flags); + data = tcpc->timer_enable_mask; + local_irq_restore(flags); + up(&tcpc->timer_enable_mask_lock); + + return data; +} + +static inline void tcpc_clear_timer_enable_mask( + struct tcpc_device *tcpc, int nr) +{ + unsigned long flags; + + down(&tcpc->timer_enable_mask_lock); + local_irq_save(flags); + tcpc->timer_enable_mask &= ~RT_MASK64(nr); + + spin_lock(&tcpc->timer_tick_lock); + tcpc->timer_tick &= ~RT_MASK64(nr); + spin_unlock(&tcpc->timer_tick_lock); + + local_irq_restore(flags); + up(&tcpc->timer_enable_mask_lock); +} + +static inline void tcpc_set_timer_enable_mask( + struct tcpc_device *tcpc, int nr) +{ + unsigned long flags; + + down(&tcpc->timer_enable_mask_lock); + local_irq_save(flags); + tcpc->timer_enable_mask |= RT_MASK64(nr); + local_irq_restore(flags); + up(&tcpc->timer_enable_mask_lock); +} + +static inline uint64_t tcpc_get_timer_tick(struct tcpc_device *tcpc) +{ + uint64_t data; + unsigned long flags; + + spin_lock_irqsave(&tcpc->timer_tick_lock, flags); + data = tcpc->timer_tick; + spin_unlock_irqrestore(&tcpc->timer_tick_lock, flags); + + return data; +} + +static inline void tcpc_clear_timer_tick(struct tcpc_device *tcpc, int nr) +{ + unsigned long flags; + + spin_lock_irqsave(&tcpc->timer_tick_lock, flags); + tcpc->timer_tick &= ~RT_MASK64(nr); + spin_unlock_irqrestore(&tcpc->timer_tick_lock, flags); +} + +static inline void tcpc_set_timer_tick(struct tcpc_device *tcpc, int nr) +{ + unsigned long flags; + + spin_lock_irqsave(&tcpc->timer_tick_lock, flags); + tcpc->timer_tick |= RT_MASK64(nr); + spin_unlock_irqrestore(&tcpc->timer_tick_lock, flags); +} + +#if TCPC_TIMER_DBG_EN || TCPC_TIMER_INFO_EN +static const char *const tcpc_timer_name[] = { +#ifdef CONFIG_USB_POWER_DELIVERY + "PD_TIMER_DISCOVER_ID", + "PD_TIMER_BIST_CONT_MODE", + "PD_TIMER_HARD_RESET_COMPLETE", + "PD_TIMER_NO_RESPONSE", + "PD_TIMER_PS_HARD_RESET", + "PD_TIMER_PS_SOURCE_OFF", + "PD_TIMER_PS_SOURCE_ON", + "PD_TIMER_PS_TRANSITION", + "PD_TIMER_SENDER_RESPONSE", + "PD_TIMER_SINK_REQUEST", + "PD_TIMER_SINK_WAIT_CAP", + "PD_TIMER_SOURCE_CAPABILITY", + "PD_TIMER_SOURCE_START", + "PD_TIMER_VCONN_ON", +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY + "PD_TIMER_VCONN_STABLE", +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + "PD_TIMER_VDM_MODE_ENTRY", + "PD_TIMER_VDM_MODE_EXIT", + "PD_TIMER_VDM_RESPONSE", + "PD_TIMER_SOURCE_TRANSITION", + "PD_TIMER_SRC_RECOVER", +#ifdef CONFIG_USB_PD_REV30 + "PD_TIMER_CK_NOT_SUPPORTED", +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + "PD_TIMER_SINK_TX", +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + "PD_TIMER_SOURCE_PPS_TIMEOUT", +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ + +/* PD_TIMER (out of spec ) */ + "PD_TIMER_VSAFE0V_DELAY", + "PD_TIMER_VSAFE0V_TOUT", + "PD_TIMER_DISCARD", + "PD_TIMER_VBUS_STABLE", + "PD_TIMER_VBUS_PRESENT", + "PD_TIMER_UVDM_RESPONSE", + "PD_TIMER_DFP_FLOW_DELAY", + "PD_TIMER_UFP_FLOW_DELAY", + "PD_TIMER_VCONN_READY", + "PD_PE_VDM_POSTPONE", + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + "PD_TIMER_DEFERRED_EVT", +#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP + "PD_TIMER_SNK_FLOW_DELAY", +#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */ +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + + "PD_TIMER_PE_IDLE_TOUT", +#endif /* CONFIG_USB_POWER_DELIVERY */ + +/* TYPEC_RT_TIMER (out of spec) */ + "TYPEC_RT_TIMER_SAFE0V_DELAY", + "TYPEC_RT_TIMER_SAFE0V_TOUT", + "TYPEC_RT_TIMER_ROLE_SWAP_START", + "TYPEC_RT_TIMER_ROLE_SWAP_STOP", + "TYPEC_RT_TIMER_STATE_CHANGE", + "TYPEC_RT_TIMER_NOT_LEGACY", + "TYPEC_RT_TIMER_LEGACY_STABLE", + "TYPEC_RT_TIMER_LEGACY_RECYCLE", + "TYPEC_RT_TIMER_AUTO_DISCHARGE", + "TYPEC_RT_TIMER_LOW_POWER_MODE", +#ifdef CONFIG_USB_POWER_DELIVERY + "TYPEC_RT_TIMER_PE_IDLE", +#ifdef CONFIG_USB_PD_WAIT_BC12 + "TYPEC_RT_TIMER_PD_WAIT_BC12", +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + "TYPEC_TIMER_ERROR_RECOVERY", +/* TYPEC-TRY-TIMER */ + "TYPEC_TRY_TIMER_DRP_TRY", + "TYPEC_TRY_TIMER_DRP_TRYWAIT", +/* TYPEC-DEBOUNCE-TIMER */ + "TYPEC_TIMER_CCDEBOUNCE", + "TYPEC_TIMER_PDDEBOUNCE", +#ifdef CONFIG_COMPATIBLE_APPLE_TA + "TYPEC_TIMER_APPLE_CC_OPEN", +#endif /* CONFIG_COMPATIBLE_APPLE_TA */ + "TYPEC_TIMER_TRYCCDEBOUNCE", + "TYPEC_TIMER_SRCDISCONNECT", + "TYPEC_TIMER_DRP_SRC_TOGGLE", +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + "TYPEC_TIMER_NORP_SRC", +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ +}; +#endif /* TCPC_TIMER_DBG_EN || TCPC_TIMER_INFO_EN */ +/* CONFIG_USB_PD_SAFE0V_DELAY */ +#ifdef CONFIG_TCPC_VSAFE0V_DETECT +#define PD_TIMER_VSAFE0V_DLY_TOUT 50 +#else +/* #ifndef CONFIG_TCPC_VSAFE0V_DETECT (equal timeout)*/ +#define PD_TIMER_VSAFE0V_DLY_TOUT 400 +#endif /* CONFIG_TCPC_VSAFE0V_DETECT */ + +/* CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_DELAY */ +#ifdef CONFIG_TCPC_VSAFE0V_DETECT +#define TYPEC_RT_TIMER_SAFE0V_DLY_TOUT 35 +#else +#define TYPEC_RT_TIMER_SAFE0V_DLY_TOUT 100 +#endif + +#define DECL_TCPC_TIMEOUT(enum, ms) \ + TIMEOUT_VAL(ms) + +#define DECL_TCPC_TIMEOUT_US(enum, us) \ + TIMEOUT_VAL_US(us) + +#define DECL_TCPC_TIMEOUT_RANGE(enum, min, max) \ + TIMEOUT_RANGE(min, max) + +static const uint32_t tcpc_timer_timeout[PD_TIMER_NR] = { +#ifdef CONFIG_USB_POWER_DELIVERY +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_DISCOVER_ID, 40, 50), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_BIST_CONT_MODE, 30, 60), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_HARD_RESET_COMPLETE, 4, 5), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_NO_RESPONSE, 4500, 5500), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_PS_HARD_RESET, 25, 35), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_PS_SOURCE_OFF, 750, 920), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_PS_SOURCE_ON, 390, 480), + +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_PS_TRANSITION, 450, 550), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_SENDER_RESPONSE, 24, 30), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_SINK_REQUEST, 100, 100), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_SINK_WAIT_CAP, 310, 620), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_SOURCE_CAPABILITY, 100, 200), + +DECL_TCPC_TIMEOUT(PD_TIMER_SOURCE_START, 20), +DECL_TCPC_TIMEOUT(PD_TIMER_VCONN_ON, 100), + +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY +DECL_TCPC_TIMEOUT(PD_TIMER_VCONN_STABLE, 50), +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_VDM_MODE_ENTRY, 40, 50), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_VDM_MODE_EXIT, 40, 50), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_VDM_RESPONSE, 24, 30), + +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_SOURCE_TRANSITION, 25, 35), +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_SRC_RECOVER, 660, 1000), + +#ifdef CONFIG_USB_PD_REV30 +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_CK_NOT_SUPPORTED, 40, 50), +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID +DECL_TCPC_TIMEOUT_RANGE(PD_TIMER_SINK_TX, 16, 20), +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE +DECL_TCPC_TIMEOUT(PD_TIMER_SOURCE_PPS_TIMEOUT, 14000), +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ + +/* PD_TIMER (out of spec) */ +DECL_TCPC_TIMEOUT(PD_TIMER_VSAFE0V_DELAY, PD_TIMER_VSAFE0V_DLY_TOUT), +DECL_TCPC_TIMEOUT(PD_TIMER_VSAFE0V_TOUT, 650), +DECL_TCPC_TIMEOUT(PD_TIMER_DISCARD, 3), + +DECL_TCPC_TIMEOUT(PD_TIMER_VBUS_STABLE, + CONFIG_USB_PD_VBUS_STABLE_TOUT), +DECL_TCPC_TIMEOUT(PD_TIMER_VBUS_PRESENT, + CONFIG_USB_PD_VBUS_PRESENT_TOUT), +DECL_TCPC_TIMEOUT(PD_TIMER_UVDM_RESPONSE, + CONFIG_USB_PD_CUSTOM_VDM_TOUT), +DECL_TCPC_TIMEOUT(PD_TIMER_DFP_FLOW_DELAY, + CONFIG_USB_PD_DFP_FLOW_DLY), +DECL_TCPC_TIMEOUT(PD_TIMER_UFP_FLOW_DELAY, + CONFIG_USB_PD_UFP_FLOW_DLY), +DECL_TCPC_TIMEOUT(PD_TIMER_VCONN_READY, + CONFIG_USB_PD_VCONN_READY_TOUT), + +DECL_TCPC_TIMEOUT_US(PD_PE_VDM_POSTPONE, 3000), + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID +DECL_TCPC_TIMEOUT(PD_TIMER_DEFERRED_EVT, 5000), +#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP +DECL_TCPC_TIMEOUT(PD_TIMER_SNK_FLOW_DELAY, + CONFIG_USB_PD_UFP_FLOW_DLY), +#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */ +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + +DECL_TCPC_TIMEOUT(PD_TIMER_PE_IDLE_TOUT, 10), +#endif /* CONFIG_USB_POWER_DELIVERY */ + +/* TYPEC_RT_TIMER (out of spec) */ +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_SAFE0V_DELAY, + TYPEC_RT_TIMER_SAFE0V_DLY_TOUT), +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_SAFE0V_TOUT, 650), + +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_ROLE_SWAP_START, 20), +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_ROLE_SWAP_STOP, + CONFIG_TYPEC_CAP_ROLE_SWAP_TOUT), + +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_STATE_CHANGE, 50), +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_NOT_LEGACY, 5000), +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_LEGACY_STABLE, 30*1000), +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_LEGACY_RECYCLE, 300*1000), + +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_DISCHARGE, CONFIG_TYPEC_CAP_DISCHARGE_TOUT), +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_LOW_POWER_MODE, 500), + +#ifdef CONFIG_USB_POWER_DELIVERY +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_PE_IDLE, 1), +#ifdef CONFIG_USB_PD_WAIT_BC12 +DECL_TCPC_TIMEOUT(TYPEC_RT_TIMER_PD_WAIT_BC12, 50), +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ +DECL_TCPC_TIMEOUT_RANGE(TYPEC_TIMER_ERROR_RECOVERY, 25, 25), + +/* TYPEC-TRY-TIMER */ +DECL_TCPC_TIMEOUT_RANGE(TYPEC_TRY_TIMER_DRP_TRY, 75, 150), +DECL_TCPC_TIMEOUT_RANGE(TYPEC_TRY_TIMER_DRP_TRYWAIT, 400, 800), + +/* TYPEC-DEBOUNCE-TIMER */ +DECL_TCPC_TIMEOUT_RANGE(TYPEC_TIMER_CCDEBOUNCE, 100, 200), +DECL_TCPC_TIMEOUT_RANGE(TYPEC_TIMER_PDDEBOUNCE, 10, 10), +#ifdef CONFIG_COMPATIBLE_APPLE_TA +DECL_TCPC_TIMEOUT_RANGE(TYPEC_TIMER_APPLE_CC_OPEN, 200, 200), +#endif /* CONFIG_COMPATIBLE_APPLE_TA */ +DECL_TCPC_TIMEOUT_RANGE(TYPEC_TIMER_TRYCCDEBOUNCE, 10, 20), +DECL_TCPC_TIMEOUT_RANGE(TYPEC_TIMER_SRCDISCONNECT, 0, 20), +DECL_TCPC_TIMEOUT(TYPEC_TIMER_DRP_SRC_TOGGLE, 60), +#ifdef CONFIG_TYPEC_CAP_NORP_SRC +DECL_TCPC_TIMEOUT(TYPEC_TIMER_NORP_SRC, 300), +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ +}; + +typedef enum hrtimer_restart (*tcpc_hrtimer_call)(struct hrtimer *timer); + +#ifdef CONFIG_USB_POWER_DELIVERY +static inline void on_pe_timer_timeout( + struct tcpc_device *tcpc, uint32_t timer_id) +{ + struct pd_event pd_event = {0}; + + pd_event.event_type = PD_EVT_TIMER_MSG; + pd_event.msg = timer_id; + pd_event.pd_msg = NULL; + + tcpc_disable_timer(tcpc, timer_id); + + switch (timer_id) { + case PD_TIMER_VDM_MODE_ENTRY: + case PD_TIMER_VDM_MODE_EXIT: + case PD_TIMER_VDM_RESPONSE: + case PD_TIMER_UVDM_RESPONSE: + pd_put_vdm_event(tcpc, &pd_event, false); + break; + + case PD_TIMER_VSAFE0V_DELAY: + pd_put_vbus_safe0v_event(tcpc); + break; + +#ifdef CONFIG_USB_PD_SAFE0V_TIMEOUT + case PD_TIMER_VSAFE0V_TOUT: + TCPC_INFO("VSafe0V TOUT (%d)\n", tcpc->vbus_level); + if (!tcpci_check_vbus_valid_from_ic(tcpc)) + pd_put_vbus_safe0v_event(tcpc); + break; +#endif /* CONFIG_USB_PD_SAFE0V_TIMEOUT */ + +#ifdef CONFIG_USB_PD_RETRY_CRC_DISCARD + case PD_TIMER_DISCARD: + tcpc->pd_discard_pending = false; + pd_put_hw_event(tcpc, PD_HW_TX_FAILED); + break; +#endif /* CONFIG_USB_PD_RETRY_CRC_DISCARD */ + +#if CONFIG_USB_PD_VBUS_STABLE_TOUT + case PD_TIMER_VBUS_STABLE: + pd_put_vbus_stable_event(tcpc); + break; +#endif /* CONFIG_USB_PD_VBUS_STABLE_TOUT */ + +#if CONFIG_USB_PD_VBUS_PRESENT_TOUT + case PD_TIMER_VBUS_PRESENT: + pd_put_vbus_present_event(tcpc); + break; +#endif /* CONFIG_USB_PD_VBUS_PRESENT_TOUT */ + + case PD_PE_VDM_POSTPONE: + tcpc->pd_postpone_vdm_timeout = true; + atomic_inc(&tcpc->pending_event); + wake_up(&tcpc->event_wait_que); + break; + + case PD_TIMER_PE_IDLE_TOUT: + TCPC_INFO("pe_idle tout\n"); + pd_put_pe_event(&tcpc->pd_port, PD_PE_IDLE); + break; + + default: + pd_put_event(tcpc, &pd_event, false); + break; + } +} +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#define TCPC_TIMER_TRIGGER() do \ +{ \ + tcpc_set_timer_tick(tcpc, index); \ + wake_up(&tcpc->timer_wait_que); \ +} while (0) + +#ifdef CONFIG_USB_POWER_DELIVERY +static enum hrtimer_restart tcpc_timer_bist_cont_mode(struct hrtimer *timer) +{ + int index = PD_TIMER_BIST_CONT_MODE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_discover_id(struct hrtimer *timer) +{ + int index = PD_TIMER_DISCOVER_ID; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart + tcpc_timer_hard_reset_complete(struct hrtimer *timer) +{ + int index = PD_TIMER_HARD_RESET_COMPLETE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_no_response(struct hrtimer *timer) +{ + int index = PD_TIMER_NO_RESPONSE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_ps_hard_reset(struct hrtimer *timer) +{ + int index = PD_TIMER_PS_HARD_RESET; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_ps_source_off(struct hrtimer *timer) +{ + int index = PD_TIMER_PS_SOURCE_OFF; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_ps_source_on(struct hrtimer *timer) +{ + int index = PD_TIMER_PS_SOURCE_ON; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_ps_transition(struct hrtimer *timer) +{ + int index = PD_TIMER_PS_TRANSITION; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_sender_response(struct hrtimer *timer) +{ + int index = PD_TIMER_SENDER_RESPONSE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_sink_request(struct hrtimer *timer) +{ + int index = PD_TIMER_SINK_REQUEST; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_sink_wait_cap(struct hrtimer *timer) +{ + int index = PD_TIMER_SINK_WAIT_CAP; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_source_capability(struct hrtimer *timer) +{ + int index = PD_TIMER_SOURCE_CAPABILITY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_source_start(struct hrtimer *timer) +{ + int index = PD_TIMER_SOURCE_START; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_vconn_on(struct hrtimer *timer) +{ + int index = PD_TIMER_VCONN_ON; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY +static enum hrtimer_restart tcpc_timer_vconn_stable(struct hrtimer *timer) +{ + int index = PD_TIMER_VCONN_STABLE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + +static enum hrtimer_restart tcpc_timer_vdm_mode_entry(struct hrtimer *timer) +{ + int index = PD_TIMER_VDM_MODE_ENTRY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_vdm_mode_exit(struct hrtimer *timer) +{ + int index = PD_TIMER_VDM_MODE_EXIT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_vdm_response(struct hrtimer *timer) +{ + int index = PD_TIMER_VDM_RESPONSE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_source_transition(struct hrtimer *timer) +{ + int index = PD_TIMER_SOURCE_TRANSITION; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_src_recover(struct hrtimer *timer) +{ + int index = PD_TIMER_SRC_RECOVER; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_USB_PD_REV30 +static enum hrtimer_restart tcpc_timer_ck_no_support(struct hrtimer *timer) +{ + int index = PD_TIMER_CK_NOT_SUPPORTED; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID +static enum hrtimer_restart tcpc_timer_sink_tx(struct hrtimer *timer) +{ + int index = PD_TIMER_SINK_TX; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE +static enum hrtimer_restart tcpc_timer_source_pps(struct hrtimer *timer) +{ + int index = PD_TIMER_SOURCE_PPS_TIMEOUT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ + +#endif /* CONFIG_USB_PD_REV30 */ + +/* PD_TIMER (out of spec )*/ +static enum hrtimer_restart tcpc_timer_vsafe0v_delay(struct hrtimer *timer) +{ + int index = PD_TIMER_VSAFE0V_DELAY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_vsafe0v_tout(struct hrtimer *timer) +{ + int index = PD_TIMER_VSAFE0V_TOUT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_pd_discard(struct hrtimer *timer) +{ + int index = PD_TIMER_DISCARD; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_vbus_stable(struct hrtimer *timer) +{ + int index = PD_TIMER_VBUS_STABLE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_vbus_present(struct hrtimer *timer) +{ + int index = PD_TIMER_VBUS_PRESENT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_uvdm_response(struct hrtimer *timer) +{ + int index = PD_TIMER_UVDM_RESPONSE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_dfp_flow_delay(struct hrtimer *timer) +{ + int index = PD_TIMER_DFP_FLOW_DELAY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_ufp_flow_delay(struct hrtimer *timer) +{ + int index = PD_TIMER_UFP_FLOW_DELAY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_vconn_ready(struct hrtimer *timer) +{ + int index = PD_TIMER_VCONN_READY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_vdm_postpone(struct hrtimer *timer) +{ + int index = PD_PE_VDM_POSTPONE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID +static enum hrtimer_restart tcpc_timer_deferred_evt(struct hrtimer *timer) +{ + int index = PD_TIMER_DEFERRED_EVT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP +static enum hrtimer_restart tcpc_timer_snk_flow_delay(struct hrtimer *timer) +{ + int index = PD_TIMER_SNK_FLOW_DELAY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} +#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */ +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + +static enum hrtimer_restart tcpc_timer_pe_idle_tout(struct hrtimer *timer) +{ + int index = PD_TIMER_PE_IDLE_TOUT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#endif /* CONFIG_USB_POWER_DELIVERY */ + +/* TYPEC_RT_TIMER (out of spec ) */ + +static enum hrtimer_restart tcpc_timer_rt_vsafe0v_delay(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_SAFE0V_DELAY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_vsafe0v_tout(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_SAFE0V_TOUT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_role_swap_start(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_ROLE_SWAP_START; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_role_swap_stop(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_ROLE_SWAP_STOP; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_legacy(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_STATE_CHANGE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_not_legacy(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_NOT_LEGACY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_legacy_stable(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_LEGACY_STABLE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_legacy_recycle(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_LEGACY_RECYCLE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_discharge(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_DISCHARGE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_rt_low_power_mode(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_LOW_POWER_MODE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_USB_POWER_DELIVERY +static enum hrtimer_restart tcpc_timer_rt_pe_idle(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_PE_IDLE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_USB_PD_WAIT_BC12 +static enum hrtimer_restart tcpc_timer_rt_pd_wait_bc12(struct hrtimer *timer) +{ + int index = TYPEC_RT_TIMER_PD_WAIT_BC12; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + +/* TYPEC-TRY-TIMER */ +static enum hrtimer_restart tcpc_timer_try_drp_try(struct hrtimer *timer) +{ + int index = TYPEC_TRY_TIMER_DRP_TRY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_try_drp_trywait(struct hrtimer *timer) +{ + int index = TYPEC_TRY_TIMER_DRP_TRYWAIT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +/* TYPEC-DEBOUNCE-TIMER */ +static enum hrtimer_restart tcpc_timer_ccdebounce(struct hrtimer *timer) +{ + int index = TYPEC_TIMER_CCDEBOUNCE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_pddebounce(struct hrtimer *timer) +{ + int index = TYPEC_TIMER_PDDEBOUNCE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_apple_cc_open(struct hrtimer *timer) +{ + int index = TYPEC_TIMER_APPLE_CC_OPEN; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_tryccdebounce(struct hrtimer *timer) +{ + int index = TYPEC_TIMER_TRYCCDEBOUNCE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_srcdisconnect(struct hrtimer *timer) +{ + int index = TYPEC_TIMER_SRCDISCONNECT; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_error_recovery(struct hrtimer *timer) +{ + int index = TYPEC_TIMER_ERROR_RECOVERY; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +static void wake_up_work_func(struct work_struct *work) +{ + struct tcpc_device *tcpc = container_of( + work, struct tcpc_device, wake_up_work.work); + + mutex_lock(&tcpc->typec_lock); + + TCPC_INFO("%s\n", __func__); +#ifdef CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY + tcpc->typec_wakeup_once = true; +#endif /* CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY */ + + tcpc_typec_enter_lpm_again(tcpc); + + mutex_unlock(&tcpc->typec_lock); + __pm_relax(tcpc->wakeup_wake_lock); +} + +static enum alarmtimer_restart + tcpc_timer_wakeup(struct alarm *alarm, ktime_t now) +{ + struct tcpc_device *tcpc = + container_of(alarm, struct tcpc_device, wake_up_timer); + + __pm_wakeup_event(tcpc->wakeup_wake_lock, 1000); + schedule_delayed_work(&tcpc->wake_up_work, 0); + return ALARMTIMER_NORESTART; +} + +static enum hrtimer_restart tcpc_timer_drp_src_toggle(struct hrtimer *timer) +{ + int index = TYPEC_TIMER_DRP_SRC_TOGGLE; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC +static enum hrtimer_restart tcpc_timer_norp_src(struct hrtimer *timer) +{ + int index = TYPEC_TIMER_NORP_SRC; + struct tcpc_device *tcpc = + container_of(timer, struct tcpc_device, tcpc_timer[index]); + + TCPC_TIMER_TRIGGER(); + return HRTIMER_NORESTART; +} +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + +static tcpc_hrtimer_call tcpc_timer_call[PD_TIMER_NR] = { +#ifdef CONFIG_USB_POWER_DELIVERY + tcpc_timer_discover_id, + tcpc_timer_bist_cont_mode, + tcpc_timer_hard_reset_complete, + tcpc_timer_no_response, + tcpc_timer_ps_hard_reset, + tcpc_timer_ps_source_off, + tcpc_timer_ps_source_on, + tcpc_timer_ps_transition, + tcpc_timer_sender_response, + tcpc_timer_sink_request, + tcpc_timer_sink_wait_cap, + tcpc_timer_source_capability, + tcpc_timer_source_start, + tcpc_timer_vconn_on, +#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY + tcpc_timer_vconn_stable, +#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */ + tcpc_timer_vdm_mode_entry, + tcpc_timer_vdm_mode_exit, + tcpc_timer_vdm_response, + tcpc_timer_source_transition, + tcpc_timer_src_recover, +#ifdef CONFIG_USB_PD_REV30 + tcpc_timer_ck_no_support, +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + tcpc_timer_sink_tx, +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE + tcpc_timer_source_pps, +#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */ +#endif /* CONFIG_USB_PD_REV30 */ + +/* PD_TIMER (out of spec )*/ + tcpc_timer_vsafe0v_delay, + tcpc_timer_vsafe0v_tout, + tcpc_timer_pd_discard, + tcpc_timer_vbus_stable, + tcpc_timer_vbus_present, + tcpc_timer_uvdm_response, + tcpc_timer_dfp_flow_delay, + tcpc_timer_ufp_flow_delay, + tcpc_timer_vconn_ready, + tcpc_timer_vdm_postpone, + +#ifdef CONFIG_USB_PD_REV30 +#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID + tcpc_timer_deferred_evt, +#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP + tcpc_timer_snk_flow_delay, +#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */ +#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */ +#endif /* CONFIG_USB_PD_REV30 */ + + tcpc_timer_pe_idle_tout, +#endif /* CONFIG_USB_POWER_DELIVERY */ + +/* TYPEC_RT_TIMER (out of spec )*/ + tcpc_timer_rt_vsafe0v_delay, + tcpc_timer_rt_vsafe0v_tout, + tcpc_timer_rt_role_swap_start, + tcpc_timer_rt_role_swap_stop, + tcpc_timer_rt_legacy, + tcpc_timer_rt_not_legacy, + tcpc_timer_rt_legacy_stable, + tcpc_timer_rt_legacy_recycle, + tcpc_timer_rt_discharge, + tcpc_timer_rt_low_power_mode, +#ifdef CONFIG_USB_POWER_DELIVERY + tcpc_timer_rt_pe_idle, +#ifdef CONFIG_USB_PD_WAIT_BC12 + tcpc_timer_rt_pd_wait_bc12, +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + tcpc_timer_error_recovery, +/* TYPEC-TRY-TIMER */ + tcpc_timer_try_drp_try, + tcpc_timer_try_drp_trywait, +/* TYPEC-DEBOUNCE-TIMER */ + tcpc_timer_ccdebounce, + tcpc_timer_pddebounce, +#ifdef CONFIG_COMPATIBLE_APPLE_TA + tcpc_timer_apple_cc_open, +#endif /* CONFIG_COMPATIBLE_APPLE_TA */ + tcpc_timer_tryccdebounce, + tcpc_timer_srcdisconnect, + tcpc_timer_drp_src_toggle, +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + tcpc_timer_norp_src, +#endif +}; + +/* + * [BLOCK] Control Timer + */ + +static void __tcpc_enable_wakeup_timer(struct tcpc_device *tcpc, bool en) +{ + int tout = 300; /* s */ + + if (en) { + TCPC_INFO("wakeup_timer\n"); + +#ifdef CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY + if (!tcpc->typec_wakeup_once) { + if (tcpc->typec_low_rp_duty_cntdown) + tout = 5; + else + tout = 20; + } +#endif /* CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY */ + + alarm_start_relative(&tcpc->wake_up_timer, ktime_set(tout, 0)); + } else + alarm_cancel(&tcpc->wake_up_timer); +} + +static inline void tcpc_reset_timer_range( + struct tcpc_device *tcpc, int start, int end) +{ + int i; + uint64_t mask; + + mask = tcpc_get_timer_enable_mask(tcpc); + + for (i = start; i < end; i++) { + if (mask & RT_MASK64(i)) { + hrtimer_try_to_cancel(&tcpc->tcpc_timer[i]); + tcpc_clear_timer_enable_mask(tcpc, i); + } + } + + if (end == PD_TIMER_NR) + __tcpc_enable_wakeup_timer(tcpc, false); +} + +void tcpc_restart_timer(struct tcpc_device *tcpc, uint32_t timer_id) +{ + tcpc_disable_timer(tcpc, timer_id); + tcpc_enable_timer(tcpc, timer_id); +} + +void tcpc_enable_wakeup_timer(struct tcpc_device *tcpc, bool en) +{ + mutex_lock(&tcpc->timer_lock); + __tcpc_enable_wakeup_timer(tcpc, en); + mutex_unlock(&tcpc->timer_lock); +} + +void tcpc_enable_timer(struct tcpc_device *tcpc, uint32_t timer_id) +{ + uint32_t r, mod, tout; + + TCPC_TIMER_EN_DBG(tcpc, timer_id); + if (timer_id >= PD_TIMER_NR) { + PD_BUG_ON(1); + return; + } + mutex_lock(&tcpc->timer_lock); + if (timer_id >= TYPEC_TIMER_START_ID) + tcpc_reset_timer_range(tcpc, TYPEC_TIMER_START_ID, PD_TIMER_NR); + + tcpc_set_timer_enable_mask(tcpc, timer_id); + + tout = tcpc_timer_timeout[timer_id]; + +#ifdef CONFIG_USB_PD_RANDOM_FLOW_DELAY + if (timer_id == PD_TIMER_DFP_FLOW_DELAY || + timer_id == PD_TIMER_UFP_FLOW_DELAY) + tout += TIMEOUT_VAL(jiffies & 0x07); +#endif /* CONFIG_USB_PD_RANDOM_FLOW_DELAY */ + + r = tout / 1000000; + mod = tout % 1000000; + + mutex_unlock(&tcpc->timer_lock); + hrtimer_start(&tcpc->tcpc_timer[timer_id], + ktime_set(r, mod*1000), HRTIMER_MODE_REL); +} + +void tcpc_disable_timer(struct tcpc_device *tcpc, uint32_t timer_id) +{ + uint64_t mask; + + mask = tcpc_get_timer_enable_mask(tcpc); + + if (timer_id >= PD_TIMER_NR) { + PD_BUG_ON(1); + return; + } + if (mask & RT_MASK64(timer_id)) { + hrtimer_try_to_cancel(&tcpc->tcpc_timer[timer_id]); + tcpc_clear_timer_enable_mask(tcpc, timer_id); + } +} + +#ifdef CONFIG_USB_POWER_DELIVERY +void tcpc_reset_pe_timer(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->timer_lock); + tcpc_reset_timer_range(tcpc, 0, PD_PE_TIMER_END_ID); + mutex_unlock(&tcpc->timer_lock); +} +#endif /* CONFIG_USB_POWER_DELIVERY */ + +void tcpc_reset_typec_debounce_timer(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->timer_lock); + tcpc_reset_timer_range(tcpc, TYPEC_TIMER_START_ID, PD_TIMER_NR); + mutex_unlock(&tcpc->timer_lock); +} + +void tcpc_reset_typec_try_timer(struct tcpc_device *tcpc) +{ + mutex_lock(&tcpc->timer_lock); + tcpc_reset_timer_range(tcpc, + TYPEC_TRY_TIMER_START_ID, TYPEC_TIMER_START_ID); + mutex_unlock(&tcpc->timer_lock); +} + +static void tcpc_handle_timer_triggered(struct tcpc_device *tcpc) +{ + int i = 0; + uint64_t triggered_timer = tcpc_get_timer_tick(tcpc); + uint64_t enable_mask = tcpc_get_timer_enable_mask(tcpc); + +#ifdef CONFIG_USB_POWER_DELIVERY + for (i = 0; i < PD_PE_TIMER_END_ID; i++) { + if (triggered_timer & RT_MASK64(i)) { + TCPC_TIMER_DBG(tcpc, i); + if (enable_mask & RT_MASK64(i)) + on_pe_timer_timeout(tcpc, i); + tcpc_clear_timer_tick(tcpc, i); + } + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + + mutex_lock(&tcpc->typec_lock); + triggered_timer = tcpc_get_timer_tick(tcpc); + enable_mask = tcpc_get_timer_enable_mask(tcpc); + for (; i < PD_TIMER_NR; i++) { + if (triggered_timer & RT_MASK64(i)) { + TCPC_TIMER_DBG(tcpc, i); + if (enable_mask & RT_MASK64(i)) + tcpc_typec_handle_timeout(tcpc, i); + tcpc_clear_timer_tick(tcpc, i); + } + } + mutex_unlock(&tcpc->typec_lock); + +} + +static int tcpc_timer_thread_fn(void *data) +{ + struct tcpc_device *tcpc = data; + struct sched_param sch_param = {.sched_priority = MAX_RT_PRIO - 1}; + + sched_setscheduler(current, SCHED_FIFO, &sch_param); + + while (true) { + wait_event(tcpc->timer_wait_que, tcpc_get_timer_tick(tcpc) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + tcpc_handle_timer_triggered(tcpc); + } + + return 0; +} + +int tcpci_timer_init(struct tcpc_device *tcpc) +{ + int i; + + pr_info("PD Timer number = %d\n", PD_TIMER_NR); + + init_waitqueue_head(&tcpc->timer_wait_que); + tcpc->timer_tick = 0; + tcpc->timer_enable_mask = 0; + tcpc->timer_task = kthread_run(tcpc_timer_thread_fn, tcpc, + "tcpc_timer_%s", tcpc->desc.name); + for (i = 0; i < PD_TIMER_NR; i++) { + hrtimer_init(&tcpc->tcpc_timer[i], + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + tcpc->tcpc_timer[i].function = tcpc_timer_call[i]; + } + tcpc->wakeup_wake_lock = + wakeup_source_register(&tcpc->dev, "tcpc_wakeup_wake_lock"); + INIT_DELAYED_WORK(&tcpc->wake_up_work, wake_up_work_func); + alarm_init(&tcpc->wake_up_timer, ALARM_REALTIME, tcpc_timer_wakeup); + + pr_info("%s : init OK\n", __func__); + return 0; +} + +int tcpci_timer_deinit(struct tcpc_device *tcpc) +{ + kthread_stop(tcpc->timer_task); + mutex_lock(&tcpc->timer_lock); + tcpc_reset_timer_range(tcpc, 0, PD_TIMER_NR); + mutex_unlock(&tcpc->timer_lock); + cancel_delayed_work_sync(&tcpc->wake_up_work); + wakeup_source_unregister(tcpc->wakeup_wake_lock); + + pr_info("%s : de init OK\n", __func__); + return 0; +} diff --git a/drivers/usb/typec/mmi_tcpc/tcpci_typec.c b/drivers/usb/typec/mmi_tcpc/tcpci_typec.c new file mode 100644 index 000000000000..caae5ef79111 --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpci_typec.c @@ -0,0 +1,2935 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * TCPC Type-C Driver for Richtek + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "inc/tcpci.h" +#include "inc/tcpci_typec.h" +#include "inc/tcpci_timer.h" + +#ifdef CONFIG_WATER_DETECTION +#ifdef CONFIG_MTK_KERNEL_POWER_OFF_CHARGING +#include +#endif /* CONFIG_MTK_KERNEL_POWER_OFF_CHARGING */ +#endif /* CONFIG_WATER_DETECTION */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE +#define CONFIG_TYPEC_CAP_TRY_STATE +#endif + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK +#undef CONFIG_TYPEC_CAP_TRY_STATE +#define CONFIG_TYPEC_CAP_TRY_STATE +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + +#define RICHTEK_PD_COMPLIANCE_FAKE_AUDIO_ACC /* For Rp3A */ +#define RICHTEK_PD_COMPLIANCE_FAKE_EMRAK_ONLY /* For Rp3A */ +#define RICHTEK_PD_COMPLIANCE_FAKE_RA_DETACH /* For Rp-DFT */ + +enum TYPEC_WAIT_PS_STATE { + TYPEC_WAIT_PS_DISABLE = 0, + TYPEC_WAIT_PS_SNK_VSAFE5V, + TYPEC_WAIT_PS_SRC_VSAFE0V, + TYPEC_WAIT_PS_SRC_VSAFE5V, +#ifdef CONFIG_TYPEC_CAP_DBGACC + TYPEC_WAIT_PS_DBG_VSAFE5V, +#endif /* CONFIG_TYPEC_CAP_DBGACC */ +}; + +enum TYPEC_ROLE_SWAP_STATE { + TYPEC_ROLE_SWAP_NONE = 0, + TYPEC_ROLE_SWAP_TO_SNK, + TYPEC_ROLE_SWAP_TO_SRC, +}; + +#if TYPEC_INFO2_ENABLE +static const char *const typec_wait_ps_name[] = { + "Disable", + "SNK_VSafe5V", + "SRC_VSafe0V", + "SRC_VSafe5V", + "DBG_VSafe5V", +}; +#endif /* TYPEC_INFO2_ENABLE */ + +static inline void typec_wait_ps_change(struct tcpc_device *tcpc, + enum TYPEC_WAIT_PS_STATE state) +{ +#if TYPEC_INFO2_ENABLE + uint8_t old_state = tcpc->typec_wait_ps_change; + uint8_t new_state = (uint8_t) state; + + if (new_state != old_state) + TYPEC_INFO2("wait_ps=%s\n", typec_wait_ps_name[new_state]); +#endif /* TYPEC_INFO2_ENABLE */ + +#ifdef CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_TIMEOUT + if (state == TYPEC_WAIT_PS_SRC_VSAFE0V) + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_SAFE0V_TOUT); +#endif /* CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_TIMEOUT */ + + if (tcpc->typec_wait_ps_change == TYPEC_WAIT_PS_SRC_VSAFE0V + && state != TYPEC_WAIT_PS_SRC_VSAFE0V) { + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_SAFE0V_DELAY); + +#ifdef CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_TIMEOUT + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_SAFE0V_TOUT); +#endif /* CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_TIMEOUT */ + } + + tcpc->typec_wait_ps_change = (uint8_t) state; +} + +/* #define TYPEC_EXIT_ATTACHED_SRC_NO_DEBOUNCE */ +#define TYPEC_EXIT_ATTACHED_SNK_VIA_VBUS + +static inline int typec_enable_low_power_mode( + struct tcpc_device *tcpc, uint8_t pull); + +#define typec_check_cc1(cc) \ + (typec_get_cc1() == cc) + +#define typec_check_cc2(cc) \ + (typec_get_cc2() == cc) + +#define typec_check_cc(cc1, cc2) \ + (typec_check_cc1(cc1) && typec_check_cc2(cc2)) + +#define typec_check_cc_both(res) \ + (typec_check_cc(res, res)) + +#define typec_check_cc_any(res) \ + (typec_check_cc1(res) || typec_check_cc2(res)) + +#define typec_is_drp_toggling() \ + (typec_get_cc1() == TYPEC_CC_DRP_TOGGLING) + +#define typec_is_cc_open() \ + typec_check_cc_both(TYPEC_CC_VOLT_OPEN) + +#define typec_is_cable_only() \ + (typec_get_cc1() + typec_get_cc2() == TYPEC_CC_VOLT_RA) + +#define typec_is_sink_with_emark() \ + (typec_get_cc1() + typec_get_cc2() == \ + TYPEC_CC_VOLT_RA+TYPEC_CC_VOLT_RD) + +#define typec_is_cc_no_res() \ + (typec_is_drp_toggling() || typec_is_cc_open()) + +static inline int typec_enable_vconn(struct tcpc_device *tcpc) +{ +#ifndef CONFIG_USB_POWER_DELIVERY + if (!typec_is_sink_with_emark()) + return 0; +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + if (tcpc->tcpc_vconn_supply == TCPC_VCONN_SUPPLY_NEVER) + return 0; +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + + return tcpci_set_vconn(tcpc, true); +} + +/* + * [BLOCK] TYPEC Connection State Definition + */ + +enum TYPEC_CONNECTION_STATE { + typec_disabled = 0, + typec_errorrecovery, + + typec_unattached_snk, + typec_unattached_src, + + typec_attachwait_snk, + typec_attachwait_src, + + typec_attached_snk, + typec_attached_src, + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + /* Require : Assert Rp + * Exit(-> Attached.SRC) : Detect Rd (tPDDebounce). + * Exit(-> TryWait.SNK) : Not detect Rd after tDRPTry + */ + typec_try_src, + + /* Require : Assert Rd + * Exit(-> Attached.SNK) : Detect Rp (tCCDebounce) and Vbus present. + * Exit(-> Unattached.SNK) : Not detect Rp (tPDDebounce) + */ + + typec_trywait_snk, + typec_trywait_snk_pe, +#endif + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + + /* Require : Assert Rd + * Wait for tDRPTry and only then begin monitoring CC. + * Exit (-> Attached.SNK) : Detect Rp (tPDDebounce) and Vbus present. + * Exit (-> TryWait.SRC) : Not detect Rp for tPDDebounce. + */ + typec_try_snk, + + /* + * Require : Assert Rp + * Exit (-> Attached.SRC) : Detect Rd (tCCDebounce) + * Exit (-> Unattached.SNK) : Not detect Rd after tDRPTry + */ + + typec_trywait_src, + typec_trywait_src_pe, +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + + typec_audioaccessory, +#ifdef CONFIG_TYPEC_CAP_DBGACC + typec_debugaccessory, +#endif /* CONFIG_TYPEC_CAP_DBGACC */ + +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK + typec_attached_dbgacc_snk, +#endif /* CONFIG_TYPEC_CAP_DBGACC_SNK */ + +#ifdef CONFIG_TYPEC_CAP_CUSTOM_SRC + typec_attached_custom_src, +#endif /* CONFIG_TYPEC_CAP_CUSTOM_SRC */ + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + typec_attached_norp_src, +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + typec_role_swap, +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + +#ifdef CONFIG_WATER_DETECTION + typec_water_protection_wait, + typec_water_protection, +#endif /* CONFIG_WATER_DETECTION */ + + typec_unattachwait_pe, /* Wait Policy Engine go to Idle */ +}; + +#if TYPEC_INFO_ENABLE || TCPC_INFO_ENABLE +static const char *const typec_state_name[] = { + "Disabled", + "ErrorRecovery", + + "Unattached.SNK", + "Unattached.SRC", + + "AttachWait.SNK", + "AttachWait.SRC", + + "Attached.SNK", + "Attached.SRC", + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + "Try.SRC", + "TryWait.SNK", + "TryWait.SNK.PE", +#endif /* CONFIG_TYPEC_CAP_TRY_SOURCE */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + "Try.SNK", + "TryWait.SRC", + "TryWait.SRC.PE", +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + + "AudioAccessory", +#ifdef CONFIG_TYPEC_CAP_DBGACC + "DebugAccessory", +#endif /* CONFIG_TYPEC_CAP_DBGACC */ + +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK + "DBGACC.SNK", +#endif /* CONFIG_TYPEC_CAP_DBGACC_SNK */ + +#ifdef CONFIG_TYPEC_CAP_CUSTOM_SRC + "Custom.SRC", +#endif /* CONFIG_TYPEC_CAP_CUSTOM_SRC */ + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + "NoRp.SRC", +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + "RoleSwap", +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + +#ifdef CONFIG_WATER_DETECTION + "WaterProtection.Wait", + "WaterProtection", +#endif /* CONFIG_WATER_DETECTION */ + + "UnattachWait.PE", +}; +#endif /* TYPEC_INFO_ENABLE || TCPC_INFO_ENABLE */ + +static inline void typec_transfer_state(struct tcpc_device *tcpc, + enum TYPEC_CONNECTION_STATE state) +{ +#if TYPEC_INFO_ENABLE + if (state >= 0 && state < ARRAY_SIZE(typec_state_name)) + TYPEC_INFO("** %s\n", typec_state_name[state]); +#endif /* TYPEC_INFO_ENABLE */ + tcpc->typec_state = (uint8_t) state; +} + +#define TYPEC_NEW_STATE(state) \ + (typec_transfer_state(tcpc, state)) + +/* + * [BLOCK] TypeC Alert Attach Status Changed + */ + +#if TYPEC_INFO_ENABLE || TYPEC_DBG_ENABLE +static const char *const typec_attach_name[] = { + "NULL", + "SINK", + "SOURCE", + "AUDIO", + "DEBUG", + + "DBGACC_SNK", + "CUSTOM_SRC", + "NORP_SRC", +}; +#endif /* TYPEC_INFO_ENABLE || TYPEC_DBG_ENABLE */ + +static int typec_alert_attach_state_change(struct tcpc_device *tcpc) +{ + int ret = 0; + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + if (tcpc->typec_legacy_cable) + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_NOT_LEGACY); + else + tcpc_restart_timer(tcpc, TYPEC_RT_TIMER_NOT_LEGACY); +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + + if (tcpc->typec_attach_old == tcpc->typec_attach_new) { + TYPEC_DBG("Attached-> %s(repeat)\n", + typec_attach_name[tcpc->typec_attach_new]); + return 0; + } + + TYPEC_INFO("Attached-> %s\n", + typec_attach_name[tcpc->typec_attach_new]); + + /*Report function */ + ret = tcpci_report_usb_port_changed(tcpc); + + tcpc->typec_attach_old = tcpc->typec_attach_new; + return ret; +} + +static inline int typec_set_drp_toggling(struct tcpc_device *tcpc) +{ + int ret; + + ret = tcpci_set_cc(tcpc, TYPEC_CC_DRP); + if (ret < 0) + return ret; + + return typec_enable_low_power_mode(tcpc, TYPEC_CC_DRP); +} + +#ifdef CONFIG_WATER_DETECTION +static int typec_check_water_status(struct tcpc_device *tcpc) +{ + int ret; + + if (!(tcpc->tcpc_flags & TCPC_FLAGS_WATER_DETECTION)) + return 0; + + ret = tcpci_is_water_detected(tcpc); + if (ret < 0) + return ret; + if (ret) { + tcpc_typec_handle_wd(tcpc, true); + return 1; + } + return 0; +} +#endif /* CONFIG_WATER_DETECTION */ + +/* + * [BLOCK] NoRpSRC Entry + */ + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC +static bool typec_try_enter_norp_src(struct tcpc_device *tcpc) +{ + if (tcpci_check_vbus_valid_from_ic(tcpc) && + typec_is_cc_no_res() && + tcpc->typec_state == typec_unattached_snk) { + TYPEC_INFO("norp_src=1\n"); + tcpc_enable_timer(tcpc, TYPEC_TIMER_NORP_SRC); + return true; + } + + return false; +} + +static void typec_unattach_wait_pe_idle_entry(struct tcpc_device *tcpc); +static bool typec_try_exit_norp_src(struct tcpc_device *tcpc) +{ + if ((!tcpci_check_vbus_valid_from_ic(tcpc) || + !typec_is_cc_no_res()) && + tcpc->typec_state == typec_attached_norp_src) { + TYPEC_INFO("norp_src=0\n"); + typec_unattach_wait_pe_idle_entry(tcpc); + typec_alert_attach_state_change(tcpc); + return true; + } + + return false; +} + +static inline int typec_norp_src_attached_entry(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_WATER_DETECTION +#ifdef CONFIG_WD_POLLING_ONLY + if (!tcpc->typec_power_ctrl) { + if (get_boot_mode() == KERNEL_POWER_OFF_CHARGING_BOOT || + get_boot_mode() == LOW_POWER_OFF_CHARGING_BOOT) + typec_check_water_status(tcpc); + + tcpci_set_usbid_polling(tcpc, false); + } +#else + if (!tcpc->typec_power_ctrl && typec_check_water_status(tcpc)) + return 0; +#endif /* CONFIG_WD_POLLING_ONLY */ +#endif /* CONFIG_WATER_DETECTION */ + + TYPEC_NEW_STATE(typec_attached_norp_src); + tcpc->typec_attach_new = TYPEC_ATTACHED_NORP_SRC; + +#ifdef CONFIG_TYPEC_CAP_A2C_C2C + tcpc->typec_a2c_cable = true; +#endif /* CONFIG_TYPEC_CAP_A2C_C2C */ + + tcpci_report_power_control(tcpc, true); + tcpci_sink_vbus(tcpc, TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_5V, 500); + + typec_alert_attach_state_change(tcpc); + return 0; +} +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + +/* + * [BLOCK] Unattached Entry + */ + +static inline int typec_try_low_power_mode(struct tcpc_device *tcpc) +{ + int ret = tcpci_set_low_power_mode( + tcpc, true, tcpc->typec_lpm_pull); + if (ret < 0) + return ret; + +#ifdef CONFIG_TCPC_LPM_CONFIRM + ret = tcpci_is_low_power_mode(tcpc); + if (ret < 0) + return ret; + + if (ret == 1) + return 0; + + if (tcpc->typec_lpm_retry == 0) { + TYPEC_INFO("TryLPM Failed\n"); + return 0; + } + + tcpc->typec_lpm_retry--; + TYPEC_DBG("RetryLPM : %d\n", tcpc->typec_lpm_retry); + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_LOW_POWER_MODE); +#endif /* CONFIG_TCPC_LPM_CONFIRM */ + + return 0; +} + +static inline int typec_enter_low_power_mode(struct tcpc_device *tcpc) +{ + int ret = 0; + +#ifdef CONFIG_TCPC_LPM_POSTPONE + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_LOW_POWER_MODE); +#else + ret = typec_try_low_power_mode(tcpc); +#endif /* CONFIG_TCPC_POSTPONE_LPM */ + + return ret; +} + +static inline int typec_enable_low_power_mode( + struct tcpc_device *tcpc, uint8_t pull) +{ + int ret = 0; + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + if (tcpc->typec_legacy_cable) { + TYPEC_DBG("LPM_LCOnly\n"); + return 0; + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + + if (tcpc->typec_cable_only) { + TYPEC_DBG("LPM_RaOnly\n"); + +#ifdef CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG + if (tcpc->tcpc_flags & TCPC_FLAGS_LPM_WAKEUP_WATCHDOG) + tcpc_enable_wakeup_timer(tcpc, true); +#endif /* CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG */ + + return 0; + } + + if (tcpc->typec_lpm != true) { + tcpc->typec_lpm = true; + tcpc->typec_lpm_retry = TCPC_LOW_POWER_MODE_RETRY; + tcpc->typec_lpm_pull = (uint8_t) pull; + ret = typec_enter_low_power_mode(tcpc); + } + + return ret; +} + +static inline int typec_disable_low_power_mode( + struct tcpc_device *tcpc) +{ + int ret = 0; + + if (tcpc->typec_lpm != false) { + tcpc->typec_lpm = false; + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_LOW_POWER_MODE); + tcpci_set_low_rp_duty(tcpc, false); + ret = tcpci_set_low_power_mode(tcpc, false, TYPEC_CC_DRP); + } + +#ifdef CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY + tcpc->typec_wakeup_once = 0; + tcpc->typec_low_rp_duty_cntdown = 0; +#endif /* CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY */ + + return ret; +} + +static void typec_unattached_power_entry(struct tcpc_device *tcpc) +{ + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + + if (tcpc->typec_power_ctrl) { + tcpci_set_vconn(tcpc, false); + tcpci_disable_vbus_control(tcpc); + tcpci_report_power_control(tcpc, false); + } +} + +static inline void typec_unattached_src_and_drp_entry(struct tcpc_device *tcpc) +{ + TYPEC_NEW_STATE(typec_unattached_src); + tcpci_set_cc(tcpc, TYPEC_CC_RP); + tcpc_enable_timer(tcpc, TYPEC_TIMER_DRP_SRC_TOGGLE); +} + +static inline void typec_unattached_snk_and_drp_entry(struct tcpc_device *tcpc) +{ + TYPEC_NEW_STATE(typec_unattached_snk); + tcpci_set_cc(tcpc, TYPEC_CC_DRP); + typec_enable_low_power_mode(tcpc, TYPEC_CC_DRP); +} + +static inline void typec_unattached_cc_entry(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + if (tcpc->typec_during_role_swap) { + TYPEC_NEW_STATE(typec_role_swap); + return; + } +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ +#ifdef CONFIG_CABLE_TYPE_DETECTION + tcpc_typec_handle_ctd(tcpc, TCPC_CABLE_TYPE_NONE); +#endif /* CONFIG_CABLE_TYPE_DETECTION */ + + tcpc->typec_role = tcpc->typec_role_new; + switch (tcpc->typec_role) { + case TYPEC_ROLE_SNK: + TYPEC_NEW_STATE(typec_unattached_snk); + tcpci_set_cc(tcpc, TYPEC_CC_RD); + typec_enable_low_power_mode(tcpc, TYPEC_CC_RD); + break; + case TYPEC_ROLE_SRC: +#ifdef CONFIG_TYPEC_CHECK_SRC_UNATTACH_OPEN + if (typec_check_cc_any(TYPEC_CC_VOLT_RD)) { + TYPEC_DBG("typec_src_unattach not open\n"); + tcpci_set_cc(tcpc, TYPEC_CC_OPEN); + usleep_rnage(5000, 6000); + } +#endif /* CONFIG_TYPEC_CHECK_SRC_UNATTACH_OPEN */ + TYPEC_NEW_STATE(typec_unattached_src); + tcpci_set_cc(tcpc, TYPEC_CC_RP); + typec_enable_low_power_mode(tcpc, TYPEC_CC_RP); + break; + case TYPEC_ROLE_TRY_SRC: + if (tcpc->typec_state == typec_errorrecovery) { + typec_unattached_src_and_drp_entry(tcpc); + break; + } + /* pass through */ + default: + switch (tcpc->typec_state) { + case typec_attachwait_snk: + case typec_audioaccessory: + typec_unattached_src_and_drp_entry(tcpc); + break; + default: + typec_unattached_snk_and_drp_entry(tcpc); + break; + } + break; + } + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + typec_try_enter_norp_src(tcpc); +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ +} + +static void typec_unattached_entry(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CAP_CUSTOM_HV + tcpc->typec_during_custom_hv = false; +#endif /* CONFIG_TYPEC_CAP_CUSTOM_HV */ + + tcpc->typec_usb_sink_curr = CONFIG_TYPEC_SNK_CURR_DFT; + + if (tcpc->typec_power_ctrl) + tcpci_set_vconn(tcpc, false); + typec_unattached_cc_entry(tcpc); + typec_unattached_power_entry(tcpc); +} + +static void typec_unattach_wait_pe_idle_entry(struct tcpc_device *tcpc) +{ + tcpc->typec_attach_new = TYPEC_UNATTACHED; + +#ifdef CONFIG_USB_POWER_DELIVERY + if (tcpc->pd_pe_running) { + TYPEC_NEW_STATE(typec_unattachwait_pe); + return; + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + + typec_unattached_entry(tcpc); +} + +static void typec_postpone_state_change(struct tcpc_device *tcpc) +{ + TYPEC_DBG("Postpone AlertChange\n"); + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_STATE_CHANGE); +} + +static void typec_cc_open_entry(struct tcpc_device *tcpc, uint8_t state) +{ + mutex_lock(&tcpc->access_lock); + TYPEC_NEW_STATE(state); + tcpc->typec_attach_new = TYPEC_UNATTACHED; + mutex_unlock(&tcpc->access_lock); + + tcpci_set_cc(tcpc, TYPEC_CC_OPEN); + typec_unattached_power_entry(tcpc); + + typec_postpone_state_change(tcpc); +} + +static inline void typec_error_recovery_entry(struct tcpc_device *tcpc) +{ + typec_cc_open_entry(tcpc, typec_errorrecovery); + tcpc_reset_typec_debounce_timer(tcpc); + tcpc_enable_timer(tcpc, TYPEC_TIMER_ERROR_RECOVERY); +} + +static inline void typec_disable_entry(struct tcpc_device *tcpc) +{ + typec_cc_open_entry(tcpc, typec_disabled); +} + +/* + * [BLOCK] Attached Entry + */ + +static inline int typec_set_polarity(struct tcpc_device *tcpc, + bool polarity) +{ + tcpc->typec_polarity = polarity; + return tcpci_set_polarity(tcpc, polarity); +} + +static inline int typec_set_plug_orient(struct tcpc_device *tcpc, + uint8_t res, bool polarity) +{ + int rv = typec_set_polarity(tcpc, polarity); + + if (rv) + return rv; + + return tcpci_set_cc(tcpc, res); +} + +static void typec_source_attached_with_vbus_entry(struct tcpc_device *tcpc) +{ + tcpc->typec_attach_new = TYPEC_ATTACHED_SRC; + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); +} + +static inline void typec_source_attached_entry(struct tcpc_device *tcpc) +{ + TYPEC_NEW_STATE(typec_attached_src); + tcpc->typec_is_attached_src = true; + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_SRC_VSAFE5V); + + tcpc_disable_timer(tcpc, TYPEC_TRY_TIMER_DRP_TRY); + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + if (tcpc->typec_during_role_swap) { + tcpc->typec_during_role_swap = TYPEC_ROLE_SWAP_NONE; + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_ROLE_SWAP_STOP); + } +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + + typec_set_plug_orient(tcpc, + tcpc->typec_local_rp_level, + typec_check_cc2(TYPEC_CC_VOLT_RD)); + + tcpci_report_power_control(tcpc, true); + typec_enable_vconn(tcpc); + tcpci_source_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SOURCE_5V, -1); +} + +static inline void typec_sink_attached_entry(struct tcpc_device *tcpc) +{ + TYPEC_NEW_STATE(typec_attached_snk); + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + + tcpc->typec_attach_new = TYPEC_ATTACHED_SNK; + +#ifdef CONFIG_TYPEC_CAP_TRY_STATE + if (tcpc->typec_role >= TYPEC_ROLE_DRP) + tcpc_reset_typec_try_timer(tcpc); +#endif /* CONFIG_TYPEC_CAP_TRY_STATE */ + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + if (tcpc->typec_during_role_swap) { + tcpc->typec_during_role_swap = TYPEC_ROLE_SWAP_NONE; + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_ROLE_SWAP_STOP); + } +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + + typec_set_plug_orient(tcpc, TYPEC_CC_RD, + !typec_check_cc2(TYPEC_CC_VOLT_OPEN)); + tcpc->typec_remote_rp_level = typec_get_cc_res(); + + tcpci_report_power_control(tcpc, true); + tcpci_sink_vbus(tcpc, TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_5V, -1); +} + +static inline void typec_custom_src_attached_entry( + struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK + TYPEC_DBG("[Warning] Same Rp (%d)\n", typec_get_cc1()); +#else + TYPEC_DBG("[Warning] CC Both Rp\n"); +#endif + +#ifdef CONFIG_TYPEC_CAP_CUSTOM_SRC + TYPEC_NEW_STATE(typec_attached_custom_src); + tcpc->typec_attach_new = TYPEC_ATTACHED_CUSTOM_SRC; + + tcpc->typec_remote_rp_level = typec_get_cc1(); + + tcpci_report_power_control(tcpc, true); + tcpci_sink_vbus(tcpc, TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_5V, -1); +#endif /* CONFIG_TYPEC_CAP_CUSTOM_SRC */ +} + +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK + +static inline uint8_t typec_get_sink_dbg_acc_rp_level( + int cc1, int cc2) +{ + if (cc2 == TYPEC_CC_VOLT_SNK_DFT) + return cc1; + + return TYPEC_CC_VOLT_SNK_DFT; +} + +static inline void typec_sink_dbg_acc_attached_entry( + struct tcpc_device *tcpc) +{ + bool polarity; + uint8_t rp_level; + + uint8_t cc1 = typec_get_cc1(); + uint8_t cc2 = typec_get_cc2(); + + if (cc1 == cc2) { + typec_custom_src_attached_entry(tcpc); + return; + } + + TYPEC_NEW_STATE(typec_attached_dbgacc_snk); + + tcpc->typec_attach_new = TYPEC_ATTACHED_DBGACC_SNK; + + polarity = cc2 > cc1; + + if (polarity) + rp_level = typec_get_sink_dbg_acc_rp_level(cc2, cc1); + else + rp_level = typec_get_sink_dbg_acc_rp_level(cc1, cc2); + + typec_set_plug_orient(tcpc, TYPEC_CC_RD, polarity); + tcpc->typec_remote_rp_level = rp_level; + + tcpci_report_power_control(tcpc, true); + tcpci_sink_vbus(tcpc, TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_5V, -1); +} +#else +static inline void typec_sink_dbg_acc_attached_entry( + struct tcpc_device *tcpc) +{ + typec_custom_src_attached_entry(tcpc); +} +#endif /* CONFIG_TYPEC_CAP_DBGACC_SNK */ + + +/* + * [BLOCK] Try.SRC / TryWait.SNK + */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + +static inline bool typec_role_is_try_src( + struct tcpc_device *tcpc) +{ + if (tcpc->typec_role != TYPEC_ROLE_TRY_SRC) + return false; + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + if (tcpc->typec_during_role_swap) + return false; +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + + return true; +} + +static inline void typec_try_src_entry(struct tcpc_device *tcpc) +{ + TYPEC_NEW_STATE(typec_try_src); + tcpc->typec_drp_try_timeout = false; + + tcpci_set_cc(tcpc, TYPEC_CC_RP); + tcpc_enable_timer(tcpc, TYPEC_TRY_TIMER_DRP_TRY); +} + +static inline void typec_trywait_snk_entry(struct tcpc_device *tcpc) +{ + TYPEC_NEW_STATE(typec_trywait_snk); + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + + tcpci_set_vconn(tcpc, false); + tcpci_set_cc(tcpc, TYPEC_CC_RD); + tcpci_source_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SOURCE_0V, 0); + tcpc_disable_timer(tcpc, TYPEC_TRY_TIMER_DRP_TRY); + + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); +} + +static inline void typec_trywait_snk_pe_entry(struct tcpc_device *tcpc) +{ + tcpc->typec_attach_new = TYPEC_UNATTACHED; + +#ifdef CONFIG_USB_POWER_DELIVERY + if (tcpc->typec_attach_old) { + TYPEC_NEW_STATE(typec_trywait_snk_pe); + return; + } +#endif + + typec_trywait_snk_entry(tcpc); +} + +#endif /* CONFIG_TYPEC_CAP_TRY_SOURCE */ + +/* + * [BLOCK] Try.SNK / TryWait.SRC + */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + +static inline bool typec_role_is_try_sink( + struct tcpc_device *tcpc) +{ + if (tcpc->typec_role != TYPEC_ROLE_TRY_SNK) + return false; + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + if (tcpc->typec_during_role_swap) + return false; +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + + return true; +} + +static inline void typec_try_snk_entry(struct tcpc_device *tcpc) +{ + TYPEC_NEW_STATE(typec_try_snk); + tcpc->typec_drp_try_timeout = false; + + tcpci_set_cc(tcpc, TYPEC_CC_RD); + tcpc_enable_timer(tcpc, TYPEC_TRY_TIMER_DRP_TRY); +} + +static inline void typec_trywait_src_entry(struct tcpc_device *tcpc) +{ + TYPEC_NEW_STATE(typec_trywait_src); + tcpc->typec_drp_try_timeout = false; + + tcpci_set_cc(tcpc, TYPEC_CC_RP); + tcpci_sink_vbus(tcpc, TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_0V, 0); + tcpc_enable_timer(tcpc, TYPEC_TRY_TIMER_DRP_TRY); +} + +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + +/* + * [BLOCK] Attach / Detach + */ + +static inline void typec_cc_snk_detect_vsafe5v_entry( + struct tcpc_device *tcpc) +{ + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + + if (!typec_check_cc_any(TYPEC_CC_VOLT_OPEN)) { /* Both Rp */ + typec_sink_dbg_acc_attached_entry(tcpc); + return; + } + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + if (typec_role_is_try_src(tcpc)) { + if (tcpc->typec_state == typec_attachwait_snk) { + typec_try_src_entry(tcpc); + return; + } + } +#endif /* CONFIG_TYPEC_CAP_TRY_SOURCE */ + + typec_sink_attached_entry(tcpc); +} + +static inline void typec_cc_snk_detect_entry(struct tcpc_device *tcpc) +{ + /* If Port Partner act as Source without VBUS, wait vSafe5V */ + if (tcpci_check_vbus_valid(tcpc)) + typec_cc_snk_detect_vsafe5v_entry(tcpc); + else + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_SNK_VSAFE5V); +} + +static inline void typec_cc_src_detect_vsafe0v_entry( + struct tcpc_device *tcpc) +{ + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + if (typec_role_is_try_sink(tcpc)) { + if (tcpc->typec_state == typec_attachwait_src) { + typec_try_snk_entry(tcpc); + return; + } + } +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + + typec_source_attached_entry(tcpc); +} + +static inline void typec_cc_src_detect_entry( + struct tcpc_device *tcpc) +{ + /* If Port Partner act as Sink with low VBUS, wait vSafe0v */ + bool vbus_absent = tcpci_check_vsafe0v(tcpc, true); + + if (vbus_absent || tcpc->typec_reach_vsafe0v) + typec_cc_src_detect_vsafe0v_entry(tcpc); + else + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_SRC_VSAFE0V); +} + +static inline void typec_cc_src_remove_entry(struct tcpc_device *tcpc) +{ + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + + tcpc->typec_is_attached_src = false; + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + if (typec_role_is_try_src(tcpc)) { + switch (tcpc->typec_state) { + case typec_attached_src: + typec_trywait_snk_pe_entry(tcpc); + return; + case typec_try_src: + typec_trywait_snk_entry(tcpc); + return; + } + } +#endif /* CONFIG_TYPEC_CAP_TRY_SOURCE */ + + typec_unattach_wait_pe_idle_entry(tcpc); +} + +static inline void typec_cc_snk_remove_entry(struct tcpc_device *tcpc) +{ + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + if (tcpc->typec_state == typec_try_snk) { + typec_trywait_src_entry(tcpc); + return; + } +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + + typec_unattach_wait_pe_idle_entry(tcpc); +} + +/* + * [BLOCK] Check Legacy Cable + */ + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + +static inline void typec_legacy_reset_cable_suspect( + struct tcpc_device *tcpc) +{ +#if TCPC_LEGACY_CABLE_SUSPECT_THD + tcpc->typec_legacy_cable_suspect = 0; +#endif /* TCPC_LEGACY_CABLE_SUSPECT_THD != 0 */ +} + +static inline void typec_legacy_reset_retry_wk( + struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + tcpc->typec_legacy_retry_wk = 0; +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ +} + +static inline void typec_legacy_enable_discharge( + struct tcpc_device *tcpc, bool en) +{ +#ifdef CONFIG_TYPEC_CAP_FORCE_DISCHARGE + if (tcpc->tcpc_flags & TCPC_FLAGS_PREFER_LEGACY2) { + mutex_lock(&tcpc->access_lock); + tcpci_enable_force_discharge(tcpc, en, 0); + mutex_unlock(&tcpc->access_lock); + } +#endif /* CONFIG_TYPEC_CAP_FORCE_DISCHARGE */ +} + +static inline void typec_legacy_keep_default_rp( + struct tcpc_device *tcpc, bool en) +{ +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + typec_legacy_enable_discharge(tcpc, en); + + if (en) { + tcpci_set_cc(tcpc, TYPEC_CC_RD); + usleep_range(1000, 2000); + tcpci_set_cc(tcpc, TYPEC_CC_RP); + usleep_range(1000, 2000); + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ +} + +static inline bool typec_legacy_charge( + struct tcpc_device *tcpc) +{ + int i, vbus_level = 0; + + TYPEC_INFO("LC->Charge\n"); + tcpci_source_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SOURCE_5V, 100); + + for (i = 0; i < 6; i++) { /* 275 ms */ + vbus_level = tcpm_inquire_vbus_level(tcpc, true); + if (vbus_level >= TCPC_VBUS_VALID) + return true; + msleep(50); + } + + TYPEC_INFO("LC->Charge Failed\n"); + return false; +} + +static inline bool typec_legacy_discharge( + struct tcpc_device *tcpc) +{ + int i, vbus_level = 0; + + TYPEC_INFO("LC->Discharge\n"); + tcpci_source_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SOURCE_0V, 0); + + for (i = 0; i < 6; i++) { /* 275 ms */ + vbus_level = tcpm_inquire_vbus_level(tcpc, true); + if (vbus_level < TCPC_VBUS_VALID) + return true; + msleep(50); + } + + TYPEC_INFO("LC->Discharge Failed\n"); + return false; +} + +static inline bool typec_legacy_suspect(struct tcpc_device *tcpc) +{ + int i = 0, vbus_level = 0; + + TYPEC_INFO("LC->Suspect\n"); + typec_legacy_reset_cable_suspect(tcpc); + + while (1) { + vbus_level = tcpm_inquire_vbus_level(tcpc, true); + if (vbus_level < TCPC_VBUS_VALID) + break; + + i++; + if (i > 3) { /* 150 ms */ + TYPEC_INFO("LC->TAIn\n"); + return false; + } + + msleep(50); + }; + + tcpci_set_cc(tcpc, TYPEC_CC_RP_1_5); + usleep_range(1000, 2000); + + return tcpci_get_cc(tcpc) != 0; +} + +static inline bool typec_legacy_stable1(struct tcpc_device *tcpc) +{ + typec_legacy_charge(tcpc); + typec_legacy_discharge(tcpc); + TYPEC_INFO("LC->Stable\n"); + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_LEGACY_STABLE); + + return true; +} + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + +static inline bool typec_is_run_legacy_stable2(struct tcpc_device *tcpc) +{ + bool run_legacy2; + uint8_t retry_max = TCPC_LEGACY_CABLE_RETRY_SOLUTION; + + run_legacy2 = tcpc->tcpc_flags & TCPC_FLAGS_PREFER_LEGACY2; + + TYPEC_INFO("LC->Retry%d\n", tcpc->typec_legacy_retry_wk++); + + if (tcpc->typec_legacy_retry_wk <= retry_max) + return run_legacy2; + + if (tcpc->typec_legacy_retry_wk > (retry_max*2)) + typec_legacy_reset_retry_wk(tcpc); + + return !run_legacy2; +} + +static inline bool typec_legacy_stable2(struct tcpc_device *tcpc) +{ + tcpc->typec_legacy_cable = 2; + TYPEC_INFO("LC->Stable2\n"); + typec_legacy_keep_default_rp(tcpc, true); + + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_LEGACY_STABLE); + +#ifdef CONFIG_TYPEC_LEGACY2_AUTO_RECYCLE + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_LEGACY_RECYCLE); +#endif /* CONFIG_TYPEC_LEGACY2_AUTO_RECYCLE */ + + return true; +} +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ + +static inline bool typec_legacy_confirm(struct tcpc_device *tcpc) +{ + TYPEC_INFO("LC->Confirm\n"); + tcpc->typec_legacy_cable = 1; + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_NOT_LEGACY); + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + if (typec_is_run_legacy_stable2(tcpc)) + return typec_legacy_stable2(tcpc); +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ + + return typec_legacy_stable1(tcpc); +} + +static inline bool typec_legacy_check_cable(struct tcpc_device *tcpc) +{ + bool check_legacy = false; + + if (tcpc->tcpc_flags & TCPC_FLAGS_DISABLE_LEGACY) + return false; + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + if (tcpc->typec_legacy_cable == 2) { + typec_unattached_src_and_drp_entry(tcpc); + return true; + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ + + if (typec_check_cc(TYPEC_CC_VOLT_RD, TYPEC_CC_VOLT_OPEN) || + typec_check_cc(TYPEC_CC_VOLT_OPEN, TYPEC_CC_VOLT_RD)) + check_legacy = true; + +#if TCPC_LEGACY_CABLE_SUSPECT_THD + if (tcpc->typec_legacy_cable_suspect < + TCPC_LEGACY_CABLE_SUSPECT_THD) + check_legacy = false; +#endif /* TCPC_LEGACY_CABLE_SUSPECT_THD */ + + if (check_legacy) { + if (typec_legacy_suspect(tcpc)) { + typec_legacy_confirm(tcpc); + return true; + } + + tcpc->typec_legacy_cable = false; + tcpci_set_cc(tcpc, TYPEC_CC_RP); + } + + return false; +} + +static inline void typec_legacy_reset_timer(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + if (tcpc->typec_legacy_cable == 2) + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_LEGACY_RECYCLE); + + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_LEGACY_STABLE); +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ +} + +static inline void typec_legacy_reach_vsafe5v(struct tcpc_device *tcpc) +{ + TYPEC_INFO("LC->Attached\n"); + tcpc->typec_legacy_cable = false; + tcpci_set_cc(tcpc, TYPEC_CC_RD); + typec_legacy_reset_timer(tcpc); +} + +static inline void typec_legacy_reach_vsafe0v(struct tcpc_device *tcpc) +{ + TYPEC_INFO("LC->Detached (PS)\n"); + tcpc->typec_legacy_cable = false; + typec_set_drp_toggling(tcpc); + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_LEGACY_STABLE); +} + +static inline void typec_legacy_handle_ps_change( + struct tcpc_device *tcpc, int vbus_level) +{ +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + if (tcpc->typec_legacy_cable != 1) + return; +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ + + if (vbus_level >= TCPC_VBUS_VALID) + typec_legacy_reach_vsafe5v(tcpc); + else if (vbus_level == TCPC_VBUS_SAFE0V) + typec_legacy_reach_vsafe0v(tcpc); +} + +static inline void typec_legacy_handle_detach(struct tcpc_device *tcpc) +{ +#if TCPC_LEGACY_CABLE_SUSPECT_THD + bool suspect_legacy = false; + + if (tcpc->typec_state == typec_attachwait_src) + suspect_legacy = true; + else if (tcpc->typec_state == typec_attached_src) { + if (tcpc->typec_attach_old != TYPEC_ATTACHED_SRC) + suspect_legacy = true; + } + + if (suspect_legacy) { + tcpc->typec_legacy_cable_suspect++; + TYPEC_INFO2("LC->Suspect: %d\n", + tcpc->typec_legacy_cable_suspect); + } +#endif /* TCPC_LEGACY_CABLE_SUSPECT_THD != 0 */ +} + +static inline int typec_legacy_handle_cc_open(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + if (tcpc->typec_legacy_cable == 2) { + typec_legacy_keep_default_rp(tcpc, false); + return 1; + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ + + return 0; +} + +static inline int typec_legacy_handle_cc_present(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + return tcpc->typec_legacy_cable == 1; +#else + return 1; +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ +} + +static inline int typec_legacy_handle_cc_change(struct tcpc_device *tcpc) +{ + int ret = 0; + + if (typec_is_cc_open() || typec_is_cable_only()) + ret = typec_legacy_handle_cc_open(tcpc); + else + ret = typec_legacy_handle_cc_present(tcpc); + + if (ret == 0) + return 0; + + TYPEC_INFO("LC->Detached (CC)\n"); + + tcpc->typec_legacy_cable = false; + typec_set_drp_toggling(tcpc); + typec_legacy_reset_timer(tcpc); + return 1; +} + +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + +/* + * [BLOCK] CC Change (after debounce) + */ + +#ifdef CONFIG_TYPEC_CAP_DBGACC +static void typec_debug_acc_attached_with_vbus_entry( + struct tcpc_device *tcpc) +{ + tcpc->typec_attach_new = TYPEC_ATTACHED_DEBUG; + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); +} +#endif /* CONFIG_TYPEC_CAP_DBGACC */ + +static inline void typec_debug_acc_attached_entry(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CAP_DBGACC + TYPEC_NEW_STATE(typec_debugaccessory); + TYPEC_DBG("[Debug] CC1&2 Both Rd\n"); + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DBG_VSAFE5V); + + tcpci_report_power_control(tcpc, true); + tcpci_source_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SOURCE_5V, -1); +#endif /* CONFIG_TYPEC_CAP_DBGACC */ +} + +#ifdef CONFIG_TYPEC_CAP_AUDIO_ACC_SINK_VBUS +static inline bool typec_audio_acc_sink_vbus( + struct tcpc_device *tcpc, bool vbus_valid) +{ + if (vbus_valid) { + tcpci_report_power_control(tcpc, true); + tcpci_sink_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_5V, 500); + } else { + tcpci_sink_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_0V, 0); + tcpci_report_power_control(tcpc, false); + } + + return true; +} +#endif /* CONFIG_TYPEC_CAP_AUDIO_ACC_SINK_VBUS */ + +static bool typec_is_fake_ra_rp30(struct tcpc_device *tcpc) +{ + if (tcpc->typec_local_cc == TYPEC_CC_RP_3_0 + || tcpc->typec_local_cc == TYPEC_CC_DRP_3_0) { + tcpci_set_cc(tcpc, TYPEC_CC_RP_DFT); + usleep_range(1000, 2000); + return tcpci_get_cc(tcpc) != 0; + } + + return false; +} + +static inline bool typec_audio_acc_attached_entry(struct tcpc_device *tcpc) +{ +#ifdef RICHTEK_PD_COMPLIANCE_FAKE_AUDIO_ACC + if (typec_is_fake_ra_rp30(tcpc)) { + TYPEC_DBG("[Audio] Fake Both Ra\n"); + if (typec_check_cc_any(TYPEC_CC_VOLT_RD)) + typec_cc_src_detect_entry(tcpc); + else + typec_cc_src_remove_entry(tcpc); + return 0; + } +#endif /* RICHTEK_PD_COMPLIANCE_FAKE_AUDIO_ACC */ + + TYPEC_NEW_STATE(typec_audioaccessory); + TYPEC_DBG("[Audio] CC1&2 Both Ra\n"); + tcpc->typec_attach_new = TYPEC_ATTACHED_AUDIO; + +#ifdef CONFIG_TYPEC_CAP_AUDIO_ACC_SINK_VBUS + if (tcpci_check_vbus_valid(tcpc)) + typec_audio_acc_sink_vbus(tcpc, true); +#endif /* CONFIG_TYPEC_CAP_AUDIO_ACC_SINK_VBUS */ + + return true; +} + +static inline bool typec_cc_change_source_entry(struct tcpc_device *tcpc) +{ + bool src_remove = false; + + switch (tcpc->typec_state) { + case typec_attached_src: + if (typec_get_cc_res() != TYPEC_CC_VOLT_RD) + src_remove = true; + break; + case typec_audioaccessory: + if (!typec_check_cc_both(TYPEC_CC_VOLT_RA)) + src_remove = true; + break; +#ifdef CONFIG_TYPEC_CAP_DBGACC + case typec_debugaccessory: + if (!typec_check_cc_both(TYPEC_CC_VOLT_RD)) + src_remove = true; + break; +#endif /* CONFIG_TYPEC_CAP_DBGACC */ + default: + if (typec_check_cc_both(TYPEC_CC_VOLT_RD)) + typec_debug_acc_attached_entry(tcpc); + else if (typec_check_cc_both(TYPEC_CC_VOLT_RA)) + typec_audio_acc_attached_entry(tcpc); + else if (typec_check_cc_any(TYPEC_CC_VOLT_RD)) + typec_cc_src_detect_entry(tcpc); + else + src_remove = true; + break; + } + + if (src_remove) + typec_cc_src_remove_entry(tcpc); + + return true; +} + +static inline bool typec_attached_snk_cc_change(struct tcpc_device *tcpc) +{ + uint8_t cc_res = typec_get_cc_res(); + + if (cc_res != tcpc->typec_remote_rp_level) { + TYPEC_INFO("RpLvl Change\n"); + tcpc->typec_remote_rp_level = cc_res; + +#ifdef CONFIG_TYPEC_CAP_CUSTOM_HV + if (tcpc->typec_during_custom_hv) + return true; +#endif /* CONFIG_TYPEC_CAP_CUSTOM_HV */ + + tcpci_sink_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_5V, -1); + } + + return true; +} + +static inline bool typec_cc_change_sink_entry(struct tcpc_device *tcpc) +{ + bool snk_remove = false; + + switch (tcpc->typec_state) { + case typec_attached_snk: + if (typec_get_cc_res() == TYPEC_CC_VOLT_OPEN) + snk_remove = true; + else + typec_attached_snk_cc_change(tcpc); + break; + +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK + case typec_attached_dbgacc_snk: + if (typec_get_cc_res() == TYPEC_CC_VOLT_OPEN) + snk_remove = true; + else + typec_attached_snk_cc_change(tcpc); + break; +#endif /* CONFIG_TYPEC_CAP_DBGACC_SNK */ + +#ifdef CONFIG_TYPEC_CAP_CUSTOM_SRC + case typec_attached_custom_src: + if (typec_check_cc_any(TYPEC_CC_VOLT_OPEN)) + snk_remove = true; + break; +#endif /* CONFIG_TYPEC_CAP_CUSTOM_SRC */ + + default: + if (!typec_is_cc_open()) + typec_cc_snk_detect_entry(tcpc); + else + snk_remove = true; + } + + if (snk_remove) + typec_cc_snk_remove_entry(tcpc); + + return true; +} + +bool tcpc_typec_is_act_as_sink_role(struct tcpc_device *tcpc) +{ + bool as_sink = true; + uint8_t cc_sum; + + switch (tcpc->typec_local_cc & 0x07) { + case TYPEC_CC_RP: + as_sink = false; + break; + case TYPEC_CC_RD: + as_sink = true; + break; + case TYPEC_CC_DRP: + cc_sum = typec_get_cc1() + typec_get_cc2(); + as_sink = (cc_sum >= TYPEC_CC_VOLT_SNK_DFT); + break; + } + + return as_sink; +} +EXPORT_SYMBOL(tcpc_typec_is_act_as_sink_role); + +static inline bool typec_handle_cc_changed_entry(struct tcpc_device *tcpc) +{ + TYPEC_INFO("[CC_Change] %d/%d\n", typec_get_cc1(), typec_get_cc2()); + + tcpc->typec_attach_new = tcpc->typec_attach_old; + + if (tcpc_typec_is_act_as_sink_role(tcpc)) + typec_cc_change_sink_entry(tcpc); + else + typec_cc_change_source_entry(tcpc); + + typec_alert_attach_state_change(tcpc); + return true; +} + +/* + * [BLOCK] Handle cc-change event (from HW) + */ + +static inline void typec_attach_wait_entry(struct tcpc_device *tcpc) +{ + bool as_sink; +#ifdef CONFIG_USB_POWER_DELIVERY + struct pd_port *pd_port = &tcpc->pd_port; +#endif /* CONFIG_USB_POWER_DELIVERY */ + + if (tcpc->typec_attach_old == TYPEC_ATTACHED_SNK || + tcpc->typec_attach_old == TYPEC_ATTACHED_DBGACC_SNK) { +#ifdef CONFIG_USB_POWER_DELIVERY + if (pd_port->pe_data.pd_connected && pd_check_rev30(pd_port)) + pd_put_sink_tx_event(tcpc, typec_get_cc_res()); +#endif /* CONFIG_USB_POWER_DELIVERY */ + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); + TYPEC_DBG("RpLvl Alert\n"); + return; + } + + if (tcpc->typec_attach_old || + tcpc->typec_state == typec_attached_src) { + tcpc_reset_typec_debounce_timer(tcpc); + TYPEC_DBG("Attached, Ignore cc_attach\n"); +#ifndef CONFIG_USB_POWER_DELIVERY + typec_enable_vconn(tcpc); +#endif /* CONFIG_USB_POWER_DELIVERY */ + return; + } + + switch (tcpc->typec_state) { + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + case typec_try_src: + tcpc_enable_timer(tcpc, TYPEC_TIMER_TRYCCDEBOUNCE); + return; + + case typec_trywait_snk: + tcpc_enable_timer(tcpc, TYPEC_TIMER_CCDEBOUNCE); + return; +#endif + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + case typec_try_snk: /* typec_drp_try_timeout = true */ + tcpc_enable_timer(tcpc, TYPEC_TIMER_TRYCCDEBOUNCE); + return; + + case typec_trywait_src: /* typec_drp_try_timeout = unknown */ + tcpc_enable_timer(tcpc, TYPEC_TIMER_TRYCCDEBOUNCE); + return; +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + +#ifdef CONFIG_USB_POWER_DELIVERY + case typec_unattachwait_pe: + TYPEC_INFO("Force PE Idle\n"); + tcpc->pd_wait_pe_idle = false; + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_PE_IDLE); + typec_unattached_power_entry(tcpc); + break; +#endif + default: + break; + } + + as_sink = tcpc_typec_is_act_as_sink_role(tcpc); + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + if (!as_sink && typec_legacy_check_cable(tcpc)) + return; +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + +#ifdef CONFIG_TYPEC_NOTIFY_ATTACHWAIT + tcpci_notify_attachwait_state(tcpc, as_sink); +#endif /* CONFIG_TYPEC_NOTIFY_ATTACHWAIT */ + + if (as_sink) + TYPEC_NEW_STATE(typec_attachwait_snk); + else { + /* Advertise Rp level before Attached.SRC Ellisys 3.1.6359 */ + tcpci_set_cc(tcpc, tcpc->typec_local_rp_level); + TYPEC_NEW_STATE(typec_attachwait_src); + } + + tcpc_enable_timer(tcpc, TYPEC_TIMER_CCDEBOUNCE); +} + +#ifdef TYPEC_EXIT_ATTACHED_SNK_VIA_VBUS +static inline int typec_attached_snk_cc_detach(struct tcpc_device *tcpc) +{ + tcpc_reset_typec_debounce_timer(tcpc); +#ifdef CONFIG_USB_POWER_DELIVERY + /* + * For Source detach during HardReset, + * However Apple TA may keep cc_open about 150 ms during HardReset + */ + if (tcpc->pd_wait_hard_reset_complete) { +#ifdef CONFIG_COMPATIBLE_APPLE_TA + TYPEC_INFO2("Detach_CC (HardReset), compatible apple TA\n"); + tcpc_enable_timer(tcpc, TYPEC_TIMER_APPLE_CC_OPEN); +#else + TYPEC_INFO2("Detach_CC (HardReset)\n"); + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); +#endif /* CONFIG_COMPATIBLE_APPLE_TA */ + } else if (tcpc->pd_port.pe_data.pd_prev_connected) { + TYPEC_INFO2("Detach_CC (PD)\n"); + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + return 0; +} +#endif /* TYPEC_EXIT_ATTACHED_SNK_VIA_VBUS */ + +static inline void typec_detach_wait_entry(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + typec_legacy_handle_detach(tcpc); +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + + switch (tcpc->typec_state) { +#ifdef TYPEC_EXIT_ATTACHED_SNK_VIA_VBUS + case typec_attached_snk: +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK + case typec_attached_dbgacc_snk: +#endif /* CONFIG_TYPEC_CAP_DBGACC_SNK */ + typec_attached_snk_cc_detach(tcpc); + break; +#endif /* TYPEC_EXIT_ATTACHED_SNK_VIA_VBUS */ + + case typec_attached_src: + tcpc_enable_timer(tcpc, TYPEC_TIMER_SRCDISCONNECT); + break; + + case typec_audioaccessory: + tcpc_enable_timer(tcpc, TYPEC_TIMER_CCDEBOUNCE); + break; + +#ifdef TYPEC_EXIT_ATTACHED_SRC_NO_DEBOUNCE + case typec_attached_src: + TYPEC_INFO("Exit Attached.SRC immediately\n"); + tcpc_reset_typec_debounce_timer(tcpc); + + /* force to terminate TX */ + tcpci_init(tcpc, true); + + typec_cc_src_remove_entry(tcpc); + typec_alert_attach_state_change(tcpc); + break; +#endif /* TYPEC_EXIT_ATTACHED_SRC_NO_DEBOUNCE */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + case typec_try_src: + if (tcpc->typec_drp_try_timeout) + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); + else { + tcpc_reset_typec_debounce_timer(tcpc); + TYPEC_DBG("[Try] Ignore cc_detach\n"); + } + break; +#endif /* CONFIG_TYPEC_CAP_TRY_SOURCE */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + case typec_trywait_src: + if (tcpc->typec_drp_try_timeout) + tcpc_enable_timer(tcpc, TYPEC_TIMER_TRYCCDEBOUNCE); + else { + tcpc_reset_typec_debounce_timer(tcpc); + TYPEC_DBG("[Try] Ignore cc_detach\n"); + } + break; +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + default: + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); + break; + } +} + +static inline bool typec_is_cc_attach(struct tcpc_device *tcpc) +{ + bool cc_attach = false; + int cc1 = typec_get_cc1(); + int cc2 = typec_get_cc2(); + int cc_res = typec_get_cc_res(); + + tcpc->typec_cable_only = false; + +#ifdef RICHTEK_PD_COMPLIANCE_FAKE_RA_DETACH + if (tcpc->typec_attach_old == TYPEC_ATTACHED_SRC + && (cc_res == TYPEC_CC_VOLT_RA) && + (tcpc->typec_local_cc == TYPEC_CC_RP_DFT)) { + + tcpci_set_cc(tcpc, TYPEC_CC_RP_1_5); + usleep_range(1000, 2000); + + if (tcpci_get_cc(tcpc)) { + TYPEC_DBG("[Detach] Fake Ra\n"); + cc1 = typec_get_cc1(); + cc2 = typec_get_cc2(); + cc_res = typec_get_cc_res(); + } + } +#endif /* RICHTEK_PD_COMPLIANCE_FAKE_RA_DETACH */ + switch (tcpc->typec_attach_old) { + case TYPEC_ATTACHED_SNK: + case TYPEC_ATTACHED_SRC: + if ((cc_res != TYPEC_CC_VOLT_OPEN) && + (cc_res != TYPEC_CC_VOLT_RA)) + cc_attach = true; + break; +#ifdef CONFIG_TYPEC_CAP_CUSTOM_SRC + case TYPEC_ATTACHED_CUSTOM_SRC: + if ((cc_res != TYPEC_CC_VOLT_OPEN) && + (cc_res != TYPEC_CC_VOLT_RA)) + cc_attach = true; + break; +#endif /* CONFIG_TYPEC_CAP_CUSTOM_SRC */ + +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK + case TYPEC_ATTACHED_DBGACC_SNK: + if ((cc_res != TYPEC_CC_VOLT_OPEN) && + (cc_res != TYPEC_CC_VOLT_RA)) + cc_attach = true; + break; +#endif /* CONFIG_TYPEC_CAP_DBGACC_SNK */ + case TYPEC_ATTACHED_AUDIO: + if (typec_check_cc_both(TYPEC_CC_VOLT_RA)) + cc_attach = true; + break; + +#ifdef CONFIG_TYPEC_CAP_DBGACC + case TYPEC_ATTACHED_DEBUG: + if (typec_check_cc_both(TYPEC_CC_VOLT_RD)) + cc_attach = true; + break; +#endif /* CONFIG_TYPEC_CAP_DBGACC */ + default: /* TYPEC_UNATTACHED */ + if (cc1 != TYPEC_CC_VOLT_OPEN) + cc_attach = true; + + if (cc2 != TYPEC_CC_VOLT_OPEN) + cc_attach = true; + + /* Cable Only, no device */ + if ((cc1+cc2) == TYPEC_CC_VOLT_RA) { +#ifdef RICHTEK_PD_COMPLIANCE_FAKE_EMRAK_ONLY + if (typec_is_fake_ra_rp30(tcpc)) { + TYPEC_DBG("[Cable] Fake Ra\n"); + if ((cc1+cc2) == TYPEC_CC_VOLT_RD) + cc_attach = true; + break; + } +#endif /* RICHTEK_PD_COMPLIANCE_FAKE_EMRAK_ONLY */ + cc_attach = false; + tcpc->typec_cable_only = true; + TYPEC_DBG("[Cable] Ra Only\n"); + } + break; + } + + return cc_attach; +} + +/** + * typec_check_false_ra_detach + * + * Check the Single Ra resistance (eMark) exists or not when + * 1) Ra_detach INT triggered. + * 2) Wakeup_Timer triggered. + * + * If reentering low-power mode and eMark still exists, + * it may cause an infinite loop. + * + * If the CC status is both open, return true; otherwise return false + * + */ + +static inline bool typec_check_false_ra_detach(struct tcpc_device *tcpc) +{ + bool drp = tcpc->typec_role >= TYPEC_ROLE_DRP; + + /* + * If the DUT is DRP and current CC status has stopped toggle, + * let cc_handler to handle it later. (after debounce) + * + * If CC is toggling, force CC to present Rp. + */ + + if (drp) { + tcpci_get_cc(tcpc); + + if (!typec_is_drp_toggling()) { + TYPEC_DBG("False_RaDetach1 (%d, %d)\n", + typec_get_cc1(), typec_get_cc2()); + return true; + } + + tcpci_set_cc(tcpc, TYPEC_CC_RP); + usleep_range(1000, 2000); + } + + /* + * Check the CC status + * Rd (device) -> let cc_handler to handle it later + * eMark Only -> Reschedule wakeup timer + * + * Open -> (true condition) + * Ready to reenter low-power mode. + * If we repeatedly enter this situation, + * it will trigger low rp duty protection. + */ + + tcpci_get_cc(tcpc); + + if (typec_is_cc_open()) + tcpc->typec_cable_only = false; + else if (typec_get_cc1() + typec_get_cc2() == TYPEC_CC_VOLT_RA) { + tcpc->typec_cable_only = true; + TYPEC_DBG("False_RaDetach2 (eMark)\n"); + } else { + tcpc->typec_cable_only = false; + TYPEC_DBG("False_RaDetach3 (%d, %d)\n", + typec_get_cc1(), typec_get_cc2()); + return true; + } + +#ifdef CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG + if (tcpc->typec_cable_only && + tcpc->tcpc_flags & TCPC_FLAGS_LPM_WAKEUP_WATCHDOG) + tcpc_enable_wakeup_timer(tcpc, true); +#endif /* CONFIG_TYPEC_CAP_LPM_WAKEUP_WATCHDOG */ + +#ifdef CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY + if (!tcpc->typec_cable_only) { + if (tcpc->typec_low_rp_duty_cntdown) + tcpci_set_low_rp_duty(tcpc, true); + else { + tcpc->typec_wakeup_once = false; + tcpc->typec_low_rp_duty_cntdown = true; + } + } +#endif /* CONFIG_TYPEC_WAKEUP_ONCE_LOW_DUTY */ + + /* + * If the DUT is DRP, force CC to toggle again. + */ + + if (drp) { + tcpci_set_cc(tcpc, TYPEC_CC_DRP); + tcpci_alert_status_clear(tcpc, + TCPC_REG_ALERT_EXT_RA_DETACH); + } + + return tcpc->typec_cable_only; +} + +int tcpc_typec_enter_lpm_again(struct tcpc_device *tcpc) +{ + bool check_ra = (tcpc->typec_lpm) || (tcpc->typec_cable_only); + + if (check_ra && typec_check_false_ra_detach(tcpc)) + return 0; + + TYPEC_DBG("RetryLPM\n"); + + tcpc->typec_lpm = true; + + tcpci_set_low_power_mode(tcpc, true, + (tcpc->typec_role != TYPEC_ROLE_SRC) ? + TYPEC_CC_DRP : TYPEC_CC_RP); + + return 0; +} + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK +static inline int typec_handle_try_sink_cc_change( + struct tcpc_device *tcpc) +{ + /* + * The port shall wait for tDRPTry and only then begin + * begin monitoring the CC1 and CC2 pins for the SNK.Rp state + */ + + if (!tcpc->typec_drp_try_timeout) { + TYPEC_DBG("[Try.SNK] Ignore CC_Alert\n"); + return 1; + } + + if (!typec_is_cc_open()) { + tcpci_notify_attachwait_state(tcpc, true); + return 0; + } + + return 0; +} +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + +static inline int typec_get_rp_present_flag(struct tcpc_device *tcpc) +{ + uint8_t rp_flag = 0; + + if (tcpc->typec_remote_cc[0] >= TYPEC_CC_VOLT_SNK_DFT + && tcpc->typec_remote_cc[0] != TYPEC_CC_DRP_TOGGLING) + rp_flag |= 1; + + if (tcpc->typec_remote_cc[1] >= TYPEC_CC_VOLT_SNK_DFT + && tcpc->typec_remote_cc[1] != TYPEC_CC_DRP_TOGGLING) + rp_flag |= 2; + + return rp_flag; +} + +static bool typec_is_cc_open_state(struct tcpc_device *tcpc) +{ + if (tcpc->typec_state == typec_errorrecovery) + return true; + + if (tcpc->typec_state == typec_disabled) + return true; + +#ifdef CONFIG_WATER_DETECTION + if ((tcpc->tcpc_flags & TCPC_FLAGS_WATER_DETECTION) && + (tcpc->typec_state == typec_water_protection_wait || + tcpc->typec_state == typec_water_protection)) + return true; +#endif /* CONFIG_WATER_DETECTION */ + + return false; +} + +static inline bool typec_is_ignore_cc_change( + struct tcpc_device *tcpc, uint8_t rp_present) +{ + if (typec_is_cc_open_state(tcpc)) + return true; + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + if (tcpc->typec_legacy_cable && + typec_legacy_handle_cc_change(tcpc)) { + return true; + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + +#ifdef CONFIG_USB_POWER_DELIVERY + if (tcpc->typec_state == typec_attachwait_snk && + typec_get_rp_present_flag(tcpc) == rp_present) { + TYPEC_DBG("[AttachWait] Ignore RpLvl Alert\n"); + return true; + } + + if (tcpc->pd_wait_pr_swap_complete) { + TYPEC_DBG("[PR.Swap] Ignore CC_Alert\n"); + return true; + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + if (tcpc->typec_state == typec_try_snk) { + if (typec_handle_try_sink_cc_change(tcpc) > 0) + return true; + } + + if (tcpc->typec_state == typec_trywait_src_pe) { + TYPEC_DBG("[Try.PE] Ignore CC_Alert\n"); + return true; + } +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + if (tcpc->typec_state == typec_trywait_snk_pe) { + TYPEC_DBG("[Try.PE] Ignore CC_Alert\n"); + return true; + } +#endif /* CONFIG_TYPEC_CAP_TRY_SOURCE */ + + return false; +} + +int tcpc_typec_handle_cc_change(struct tcpc_device *tcpc) +{ + int ret; + uint8_t rp_present; + +#ifdef CONFIG_WATER_DETECTION + /* For ellisys rp/rp to rp/open */ + u8 typec_state_old = tcpc->typec_state; +#endif /* CONFIG_WATER_DETECTION */ + + rp_present = typec_get_rp_present_flag(tcpc); + + ret = tcpci_get_cc(tcpc); + if (ret < 0) + return ret; + + TYPEC_INFO("[CC_Alert] %d/%d\n", typec_get_cc1(), typec_get_cc2()); + + if (typec_is_cc_no_res()) { + TYPEC_DBG("[Warning] CC No Res\n"); + if (tcpc->typec_lpm && !tcpc->typec_cable_only) + typec_enter_low_power_mode(tcpc); + if (typec_is_drp_toggling()) + return 0; + } + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + if (typec_try_exit_norp_src(tcpc)) + return 0; +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + + if (typec_is_ignore_cc_change(tcpc, rp_present)) + return 0; + + if (tcpc->typec_state == typec_attachwait_snk + || tcpc->typec_state == typec_attachwait_src) + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + + if (typec_is_cc_attach(tcpc)) { + typec_disable_low_power_mode(tcpc); + typec_attach_wait_entry(tcpc); +#ifdef CONFIG_WATER_DETECTION + if (typec_state_old == typec_unattached_snk || + typec_state_old == typec_unattached_src) { +#ifdef CONFIG_WD_POLLING_ONLY + if (get_boot_mode() == KERNEL_POWER_OFF_CHARGING_BOOT + || get_boot_mode() == LOW_POWER_OFF_CHARGING_BOOT) + typec_check_water_status(tcpc); +#else + typec_check_water_status(tcpc); +#endif /* CONFIG_WD_POLLING_ONLY */ + } +#endif /* CONFIG_WATER_DETECTION */ + } else + typec_detach_wait_entry(tcpc); + + return 0; +} + +/* + * [BLOCK] Handle timeout event + */ + +#ifdef CONFIG_TYPEC_CAP_TRY_STATE +static inline int typec_handle_drp_try_timeout(struct tcpc_device *tcpc) +{ + bool src_detect = false, en_timer; + + tcpc->typec_drp_try_timeout = true; + tcpc_disable_timer(tcpc, TYPEC_TRY_TIMER_DRP_TRY); + + if (typec_is_drp_toggling()) { + TYPEC_DBG("[Warning] DRP Toggling\n"); + return 0; + } + + src_detect = typec_check_cc_any(TYPEC_CC_VOLT_RD); + + switch (tcpc->typec_state) { +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + case typec_try_src: + en_timer = !src_detect; + break; +#endif /* CONFIG_TYPEC_CAP_TRY_SOURCE */ + +#ifdef CONFIG_TYPEC_CAP_TRY_SINK + case typec_trywait_src: + en_timer = !src_detect; + break; + + case typec_try_snk: + en_timer = true; + if (!typec_is_cc_open()) + tcpci_notify_attachwait_state(tcpc, true); + break; +#endif /* CONFIG_TYPEC_CAP_TRY_SINK */ + + default: + en_timer = false; + break; + } + + if (en_timer) + tcpc_enable_timer(tcpc, TYPEC_TIMER_TRYCCDEBOUNCE); + + return 0; +} +#endif /* CONFIG_TYPEC_CAP_TRY_STATE */ + +static inline int typec_handle_debounce_timeout(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + if (typec_is_cc_no_res() && tcpci_check_vbus_valid(tcpc) + && (tcpc->typec_state == typec_unattached_snk)) + return typec_norp_src_attached_entry(tcpc); +#endif + + if (typec_is_drp_toggling()) { + TYPEC_DBG("[Warning] DRP Toggling\n"); + return 0; + } + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + tcpc_disable_timer(tcpc, TYPEC_RT_TIMER_STATE_CHANGE); +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + + typec_handle_cc_changed_entry(tcpc); + return 0; +} + +static inline int typec_handle_error_recovery_timeout( + struct tcpc_device *tcpc) +{ +#ifdef CONFIG_USB_POWER_DELIVERY + tcpc->pd_wait_pe_idle = false; +#endif /* CONFIG_USB_POWER_DELIVERY */ + + typec_unattach_wait_pe_idle_entry(tcpc); + typec_alert_attach_state_change(tcpc); + return 0; +} + +#ifdef CONFIG_USB_POWER_DELIVERY +static inline int typec_handle_pe_idle(struct tcpc_device *tcpc) +{ + switch (tcpc->typec_state) { + +#ifdef CONFIG_TYPEC_CAP_TRY_SOURCE + case typec_trywait_snk_pe: + typec_trywait_snk_entry(tcpc); + break; +#endif + + case typec_unattachwait_pe: + typec_unattached_entry(tcpc); + break; + + default: + TYPEC_DBG("Dummy pe_idle\n"); + break; + } + + return 0; +} + +#ifdef CONFIG_USB_PD_WAIT_BC12 +static inline void typec_handle_pd_wait_bc12(struct tcpc_device *tcpc) +{ + int ret = 0; + uint8_t type = TYPEC_UNATTACHED; + union power_supply_propval val = {.intval = 0}; + + mutex_lock(&tcpc->access_lock); + + type = tcpc->typec_attach_new; + ret = power_supply_get_property(tcpc->usb_psy, + POWER_SUPPLY_PROP_REAL_TYPE, &val); + TYPEC_INFO("type=%d, ret,chg_type=%d,%d, count=%d\n", type, + ret, val.intval, tcpc->pd_wait_bc12_count); + + if (type != TYPEC_ATTACHED_SNK && type != TYPEC_ATTACHED_DBGACC_SNK) + goto out; + + if ((ret >= 0 && val.intval != POWER_SUPPLY_TYPE_UNKNOWN) || + tcpc->pd_wait_bc12_count >= 20) { + __pd_put_cc_attached_event(tcpc, type); + } else { + tcpc->pd_wait_bc12_count++; + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_PD_WAIT_BC12); + } +out: + mutex_unlock(&tcpc->access_lock); +} +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + +static inline int typec_handle_src_reach_vsafe0v(struct tcpc_device *tcpc) +{ + if (typec_is_drp_toggling()) { + TYPEC_DBG("[Warning] DRP Toggling\n"); + return 0; + } + + tcpc->typec_reach_vsafe0v = true; + typec_cc_src_detect_vsafe0v_entry(tcpc); + typec_alert_attach_state_change(tcpc); + return 0; +} + +static inline int typec_handle_src_toggle_timeout(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + if (tcpc->typec_during_role_swap) + return 0; +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + + if (tcpc->typec_state == typec_unattached_src) { + typec_unattached_snk_and_drp_entry(tcpc); + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + typec_try_enter_norp_src(tcpc); +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + } + + return 0; +} + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP +static inline int typec_handle_role_swap_start(struct tcpc_device *tcpc) +{ + uint8_t role_swap = tcpc->typec_during_role_swap; + + if (role_swap == TYPEC_ROLE_SWAP_TO_SNK) { + TYPEC_INFO("Role Swap to Sink\n"); + tcpci_set_cc(tcpc, TYPEC_CC_RD); + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_ROLE_SWAP_STOP); + } else if (role_swap == TYPEC_ROLE_SWAP_TO_SRC) { + TYPEC_INFO("Role Swap to Source\n"); + tcpci_set_cc(tcpc, TYPEC_CC_RP); + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_ROLE_SWAP_STOP); + } + + return 0; +} + +static inline int typec_handle_role_swap_stop(struct tcpc_device *tcpc) +{ + if (tcpc->typec_during_role_swap) { + TYPEC_INFO("TypeC Role Swap Failed\n"); + tcpc->typec_during_role_swap = TYPEC_ROLE_SWAP_NONE; + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); + } + + return 0; +} +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + +int tcpc_typec_handle_timeout(struct tcpc_device *tcpc, uint32_t timer_id) +{ + int ret = 0; + +#ifdef CONFIG_TYPEC_CAP_TRY_STATE + if (timer_id == TYPEC_TRY_TIMER_DRP_TRY) + return typec_handle_drp_try_timeout(tcpc); +#endif /* CONFIG_TYPEC_CAP_TRY_STATE */ + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + if (timer_id == TYPEC_TIMER_DRP_SRC_TOGGLE && + (tcpc->typec_state != typec_unattached_src)) { + TCPC_DBG("Dummy SRC_TOGGLE\n"); + return 0; + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + + if (timer_id >= TYPEC_TIMER_START_ID) + tcpc_reset_typec_debounce_timer(tcpc); + else if (timer_id >= TYPEC_RT_TIMER_START_ID) + tcpc_disable_timer(tcpc, timer_id); + + if (timer_id == TYPEC_TIMER_ERROR_RECOVERY) + return typec_handle_error_recovery_timeout(tcpc); + else if (timer_id == TYPEC_RT_TIMER_STATE_CHANGE) + return typec_alert_attach_state_change(tcpc); + else if (typec_is_cc_open_state(tcpc)) { + TYPEC_DBG("[Open] Ignore timer_evt\n"); + return 0; + } + +#ifdef CONFIG_USB_POWER_DELIVERY + if (tcpc->pd_wait_pr_swap_complete) { + TYPEC_DBG("[PR.Swap] Ignore timer_evt\n"); + return 0; + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + + switch (timer_id) { +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_COMPATIBLE_APPLE_TA + case TYPEC_TIMER_APPLE_CC_OPEN: +#endif /* CONFIG_COMPATIBLE_APPLE_TA */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + case TYPEC_TIMER_CCDEBOUNCE: + case TYPEC_TIMER_PDDEBOUNCE: + case TYPEC_TIMER_TRYCCDEBOUNCE: + case TYPEC_TIMER_SRCDISCONNECT: + /* fall through */ +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + case TYPEC_TIMER_NORP_SRC: +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + ret = typec_handle_debounce_timeout(tcpc); + break; + +#ifdef CONFIG_USB_POWER_DELIVERY + case TYPEC_RT_TIMER_PE_IDLE: + ret = typec_handle_pe_idle(tcpc); + break; +#ifdef CONFIG_USB_PD_WAIT_BC12 + case TYPEC_RT_TIMER_PD_WAIT_BC12: + typec_handle_pd_wait_bc12(tcpc); + break; +#endif /* CONFIG_USB_PD_WAIT_BC12 */ +#endif /* CONFIG_USB_POWER_DELIVERY */ + +#ifdef CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_DELAY + case TYPEC_RT_TIMER_SAFE0V_DELAY: + ret = typec_handle_src_reach_vsafe0v(tcpc); + break; +#endif /* CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_DELAY */ + + case TYPEC_RT_TIMER_LOW_POWER_MODE: + if (tcpc->typec_lpm) + typec_try_low_power_mode(tcpc); + break; + +#ifdef CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_TIMEOUT + case TYPEC_RT_TIMER_SAFE0V_TOUT: + TCPC_INFO("VSafe0V TOUT (%d)\n", tcpc->vbus_level); + + if (!tcpci_check_vbus_valid_from_ic(tcpc)) + ret = tcpc_typec_handle_vsafe0v(tcpc); + break; +#endif /* CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_TIMEOUT */ + + case TYPEC_TIMER_DRP_SRC_TOGGLE: + ret = typec_handle_src_toggle_timeout(tcpc); + break; + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + case TYPEC_RT_TIMER_ROLE_SWAP_START: + typec_handle_role_swap_start(tcpc); + break; + + case TYPEC_RT_TIMER_ROLE_SWAP_STOP: + typec_handle_role_swap_stop(tcpc); + break; +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + + case TYPEC_RT_TIMER_DISCHARGE: + if (!tcpc->typec_power_ctrl) { + mutex_lock(&tcpc->access_lock); + tcpci_enable_auto_discharge(tcpc, false); + tcpci_enable_force_discharge(tcpc, false, 0); + mutex_unlock(&tcpc->access_lock); + } + break; + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + case TYPEC_RT_TIMER_NOT_LEGACY: + tcpc->typec_legacy_cable = false; + typec_legacy_reset_retry_wk(tcpc); + typec_legacy_reset_cable_suspect(tcpc); + break; + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE2 + case TYPEC_RT_TIMER_LEGACY_STABLE: + if (tcpc->typec_legacy_cable) + tcpc->typec_legacy_retry_wk--; + break; + +#ifdef CONFIG_TYPEC_LEGACY2_AUTO_RECYCLE + case TYPEC_RT_TIMER_LEGACY_RECYCLE: + if (tcpc->typec_legacy_cable == 2) { + TYPEC_INFO("LC->Recycle\n"); + tcpc->typec_legacy_cable = false; + typec_legacy_keep_default_rp(tcpc, false); + typec_set_drp_toggling(tcpc); + } + break; +#endif /* CONFIG_TYPEC_LEGACY2_AUTO_RECYCLE */ +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE2 */ +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + } + + return ret; +} + +/* + * [BLOCK] Handle ps-change event + */ + +static inline int typec_handle_vbus_present(struct tcpc_device *tcpc) +{ + switch (tcpc->typec_wait_ps_change) { + case TYPEC_WAIT_PS_SNK_VSAFE5V: + typec_cc_snk_detect_vsafe5v_entry(tcpc); + typec_alert_attach_state_change(tcpc); + break; + case TYPEC_WAIT_PS_SRC_VSAFE5V: + typec_source_attached_with_vbus_entry(tcpc); + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + if (typec_get_cc_res() != TYPEC_CC_VOLT_RD) { + typec_postpone_state_change(tcpc); + break; + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + + typec_alert_attach_state_change(tcpc); + break; +#ifdef CONFIG_TYPEC_CAP_DBGACC + case TYPEC_WAIT_PS_DBG_VSAFE5V: + typec_debug_acc_attached_with_vbus_entry(tcpc); + typec_alert_attach_state_change(tcpc); + break; +#endif /* CONFIG_TYPEC_CAP_DBGACC */ + } + + return 0; +} + +static inline int typec_attached_snk_vbus_absent(struct tcpc_device *tcpc) +{ +#ifdef TYPEC_EXIT_ATTACHED_SNK_VIA_VBUS +#ifdef CONFIG_USB_POWER_DELIVERY +#ifdef CONFIG_USB_PD_DIRECT_CHARGE + if (tcpc->pd_during_direct_charge && + !tcpci_check_vsafe0v(tcpc, true)) { + TYPEC_DBG("Ignore vbus_absent(snk), DirectCharge\n"); + return 0; + } +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + + if (tcpc->pd_wait_hard_reset_complete) { +#ifdef CONFIG_COMPATIBLE_APPLE_TA + TYPEC_DBG("Ignore vbus_absent(snk) and CC, HReset(apple)\n"); + return 0; +#else + if (typec_get_cc_res() != TYPEC_CC_VOLT_OPEN) { + TYPEC_DBG( + "Ignore vbus_absent(snk), HReset & CC!=0\n"); + return 0; + } +#endif /* CONFIG_COMPATIBLE_APPLE_TA */ + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + + typec_unattach_wait_pe_idle_entry(tcpc); + typec_alert_attach_state_change(tcpc); +#endif /* TYPEC_EXIT_ATTACHED_SNK_VIA_VBUS */ + + return 0; +} + + +static inline int typec_handle_vbus_absent(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_USB_POWER_DELIVERY + if (tcpc->pd_wait_pr_swap_complete) { + TYPEC_DBG("[PR.Swap] Ignore vbus_absent\n"); + return 0; + } +#endif /* CONFIG_USB_POWER_DELIVERY */ + + switch (tcpc->typec_state) { + case typec_attached_snk: +#ifdef CONFIG_TYPEC_CAP_DBGACC_SNK + case typec_attached_dbgacc_snk: +#endif /* CONFIG_TYPEC_CAP_DBGACC_SNK */ + typec_attached_snk_vbus_absent(tcpc); + break; + default: + break; + } + +#ifndef CONFIG_TCPC_VSAFE0V_DETECT + tcpc_typec_handle_vsafe0v(tcpc); +#endif /* CONFIG_TCPC_VSAFE0V_DETECT */ + + return 0; +} + +int tcpc_typec_handle_ps_change(struct tcpc_device *tcpc, int vbus_level) +{ + tcpc->typec_reach_vsafe0v = false; + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + if (tcpc->typec_legacy_cable) { + typec_legacy_handle_ps_change(tcpc, vbus_level); + return 0; + } +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + if (!typec_try_enter_norp_src(tcpc)) + if (typec_try_exit_norp_src(tcpc)) + return 0; +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + + if (typec_is_cc_no_res()) { + TYPEC_DBG("[Warning] CC No Res\n"); + if (tcpc->typec_lpm && !tcpc->typec_cable_only) + typec_enter_low_power_mode(tcpc); + if (typec_is_drp_toggling()) + return 0; + } + +#ifdef CONFIG_TYPEC_CAP_AUDIO_ACC_SINK_VBUS + if (tcpc->typec_state == typec_audioaccessory) { + return typec_audio_acc_sink_vbus( + tcpc, vbus_level >= TCPC_VBUS_VALID); + } +#endif /* CONFIG_TYPEC_CAP_AUDIO_ACC_SINK_VBUS */ + + if (vbus_level >= TCPC_VBUS_VALID) + return typec_handle_vbus_present(tcpc); + + return typec_handle_vbus_absent(tcpc); +} + +/* + * [BLOCK] Handle PE event + */ + +#ifdef CONFIG_USB_POWER_DELIVERY + +int tcpc_typec_handle_pe_pr_swap(struct tcpc_device *tcpc) +{ + int ret = 0; + + mutex_lock(&tcpc->typec_lock); + switch (tcpc->typec_state) { + case typec_attached_snk: + TYPEC_NEW_STATE(typec_attached_src); + tcpc->typec_is_attached_src = true; + tcpc->typec_attach_new = TYPEC_ATTACHED_SRC; + tcpci_set_cc(tcpc, tcpc->typec_local_rp_level); + break; + case typec_attached_src: + TYPEC_NEW_STATE(typec_attached_snk); + tcpc->typec_is_attached_src = false; + tcpc->typec_attach_new = TYPEC_ATTACHED_SNK; + tcpci_set_cc(tcpc, TYPEC_CC_RD); + break; + default: + break; + } + + typec_alert_attach_state_change(tcpc); + mutex_unlock(&tcpc->typec_lock); + return ret; +} + +#endif /* CONFIG_USB_POWER_DELIVERY */ + +/* + * [BLOCK] Handle reach vSafe0V event + */ + +int tcpc_typec_handle_vsafe0v(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_WATER_DETECTION + if ((tcpc->tcpc_flags & TCPC_FLAGS_WATER_DETECTION) && + tcpc->typec_state == typec_water_protection_wait) { + TYPEC_NEW_STATE(typec_water_protection); + tcpci_set_water_protection(tcpc, true); + return 0; + } +#endif /* CONFIG_WATER_DETECTION */ + + if (tcpc->typec_wait_ps_change == TYPEC_WAIT_PS_SRC_VSAFE0V) { +#ifdef CONFIG_TYPEC_ATTACHED_SRC_SAFE0V_DELAY + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_SAFE0V_DELAY); +#else + typec_handle_src_reach_vsafe0v(tcpc); +#endif + } + + return 0; +} + +/* + * [BLOCK] TCPCI TypeC I/F + */ + +#if TYPEC_INFO_ENABLE +static const char *const typec_role_name[] = { + "UNKNOWN", + "SNK", + "SRC", + "DRP", + "TrySRC", + "TrySNK", +}; +#endif /* TYPEC_INFO_ENABLE */ + +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP +int tcpc_typec_swap_role(struct tcpc_device *tcpc) +{ + if (tcpc->typec_role < TYPEC_ROLE_DRP) + return TCPM_ERROR_NOT_DRP_ROLE; + + if (tcpc->typec_during_role_swap) + return TCPM_ERROR_DURING_ROLE_SWAP; + + switch (tcpc->typec_attach_old) { + case TYPEC_ATTACHED_SNK: + tcpc->typec_during_role_swap = TYPEC_ROLE_SWAP_TO_SRC; + break; + case TYPEC_ATTACHED_SRC: + tcpc->typec_during_role_swap = TYPEC_ROLE_SWAP_TO_SNK; + break; + } + + if (tcpc->typec_during_role_swap) { + TYPEC_INFO("TypeC Role Swap Start\n"); + tcpci_set_cc(tcpc, TYPEC_CC_OPEN); + tcpc_enable_timer(tcpc, TYPEC_RT_TIMER_ROLE_SWAP_START); + return TCPM_SUCCESS; + } + + return TCPM_ERROR_UNATTACHED; +} +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ + +int tcpc_typec_set_rp_level(struct tcpc_device *tcpc, uint8_t res) +{ + switch (res) { + case TYPEC_CC_RP_DFT: + case TYPEC_CC_RP_1_5: + case TYPEC_CC_RP_3_0: + TYPEC_INFO("TypeC-Rp: %d\n", res); + tcpc->typec_local_rp_level = res; + break; + + default: + TYPEC_INFO("TypeC-Unknown-Rp (%d)\n", res); + return -EINVAL; + } + +#ifdef CONFIG_USB_PD_DBG_ALWAYS_LOCAL_RP + tcpci_set_cc(tcpc, tcpc->typec_local_rp_level); +#else + if ((tcpc->typec_attach_old != TYPEC_UNATTACHED) && + (tcpc->typec_attach_new != TYPEC_UNATTACHED)) { + return tcpci_set_cc(tcpc, res); + } +#endif + + return 0; +} + +int tcpc_typec_error_recovery(struct tcpc_device *tcpc) +{ + if (tcpc->typec_state != typec_errorrecovery) + typec_error_recovery_entry(tcpc); + + return 0; +} + +int tcpc_typec_disable(struct tcpc_device *tcpc) +{ + if (tcpc->typec_state != typec_disabled) + typec_disable_entry(tcpc); + + return 0; +} + +int tcpc_typec_enable(struct tcpc_device *tcpc) +{ + if (tcpc->typec_state == typec_disabled) + typec_unattached_entry(tcpc); + + return 0; +} + +int tcpc_typec_change_role( + struct tcpc_device *tcpc, uint8_t typec_role, bool postpone) +{ + if (typec_role == TYPEC_ROLE_UNKNOWN || + typec_role >= TYPEC_ROLE_NR) { + TYPEC_INFO("Wrong TypeC-Role: %d\n", typec_role); + return -EINVAL; + } + + if (tcpc->typec_role_new == typec_role) { + TYPEC_INFO("typec_new_role: %s is the same\n", + typec_role_name[typec_role]); + return 0; + } + tcpc->typec_role_new = typec_role; + + TYPEC_INFO("typec_new_role: %s\n", typec_role_name[typec_role]); + + if (!postpone || tcpc->typec_attach_old == TYPEC_UNATTACHED) + return tcpc_typec_error_recovery(tcpc); + else + return 0; +} + +#ifdef CONFIG_TYPEC_CAP_POWER_OFF_CHARGE +static int typec_init_power_off_charge(struct tcpc_device *tcpc) +{ + bool cc_open; + int ret = tcpci_get_cc(tcpc); + + if (ret < 0) + return ret; + + if (tcpc->typec_role == TYPEC_ROLE_SRC) + return 0; + + cc_open = typec_is_cc_open(); + +#ifndef CONFIG_TYPEC_CAP_NORP_SRC + if (cc_open) + return 0; +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + + if (!tcpci_check_vbus_valid(tcpc)) + return 0; + + TYPEC_INFO2("PowerOffCharge\n"); + + TYPEC_NEW_STATE(typec_unattached_snk); + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_DISABLE); + + tcpci_set_cc(tcpc, TYPEC_CC_DRP); + typec_enable_low_power_mode(tcpc, TYPEC_CC_DRP); + usleep_range(1000, 2000); + +#ifdef CONFIG_TYPEC_CAP_NORP_SRC + if (cc_open) { + tcpc_enable_timer(tcpc, TYPEC_TIMER_PDDEBOUNCE); + return 1; + } +#endif /* CONFIG_TYPEC_CAP_NORP_SRC */ + + tcpci_set_cc(tcpc, TYPEC_CC_RD); + + return 1; +} +#endif /* CONFIG_TYPEC_CAP_POWER_OFF_CHARGE */ + +int tcpc_typec_init(struct tcpc_device *tcpc, uint8_t typec_role) +{ + int ret = 0; + + if (typec_role == TYPEC_ROLE_UNKNOWN || + typec_role >= TYPEC_ROLE_NR) { + TYPEC_INFO("Wrong TypeC-Role: %d\n", typec_role); + return -EINVAL; + } + + TYPEC_INFO("typec_init: %s\n", typec_role_name[typec_role]); + + tcpc->typec_role = typec_role; + tcpc->typec_role_new = typec_role; + tcpc->typec_attach_new = TYPEC_UNATTACHED; + tcpc->typec_attach_old = TYPEC_UNATTACHED; + + tcpc->typec_remote_cc[0] = TYPEC_CC_VOLT_OPEN; + tcpc->typec_remote_cc[1] = TYPEC_CC_VOLT_OPEN; + + mutex_lock(&tcpc->access_lock); + tcpc->wake_lock_pd = 0; + tcpc->wake_lock_user = true; + mutex_unlock(&tcpc->access_lock); + tcpc->typec_usb_sink_curr = CONFIG_TYPEC_SNK_CURR_DFT; + +#ifdef CONFIG_TYPEC_CAP_CUSTOM_HV + tcpc->typec_during_custom_hv = false; +#endif /* CONFIG_TYPEC_CAP_CUSTOM_HV */ + +#ifdef CONFIG_TYPEC_CHECK_LEGACY_CABLE + tcpc->typec_legacy_cable = false; + typec_legacy_reset_retry_wk(tcpc); + typec_legacy_reset_cable_suspect(tcpc); +#endif /* CONFIG_TYPEC_CHECK_LEGACY_CABLE */ + +#ifdef CONFIG_TYPEC_CAP_POWER_OFF_CHARGE + ret = typec_init_power_off_charge(tcpc); + if (ret != 0) + return ret; +#endif /* CONFIG_TYPEC_CAP_POWER_OFF_CHARGE */ + +#ifdef CONFIG_TYPEC_POWER_CTRL_INIT + tcpc->typec_power_ctrl = true; +#endif /* CONFIG_TYPEC_POWER_CTRL_INIT */ + + typec_unattached_entry(tcpc); + return ret; +} + +void tcpc_typec_deinit(struct tcpc_device *tcpc) +{ +} + +#ifdef CONFIG_WATER_DETECTION +int tcpc_typec_handle_wd(struct tcpc_device *tcpc, bool wd) +{ + int ret = 0; + + if (!(tcpc->tcpc_flags & TCPC_FLAGS_WATER_DETECTION)) + return 0; + + TYPEC_INFO("%s %d\n", __func__, wd); + if (!wd) { + tcpci_set_water_protection(tcpc, false); + tcpc_typec_error_recovery(tcpc); + goto out; + } + +#ifdef CONFIG_MTK_KERNEL_POWER_OFF_CHARGING + ret = get_boot_mode(); + if (ret == KERNEL_POWER_OFF_CHARGING_BOOT || + ret == LOW_POWER_OFF_CHARGING_BOOT) { + TYPEC_INFO("KPOC does not enter water protection\n"); + goto out; + } +#endif /* CONFIG_MTK_KERNEL_POWER_OFF_CHARGING */ + + tcpc->typec_attach_new = TYPEC_UNATTACHED; + ret = tcpci_set_cc(tcpc, TYPEC_CC_OPEN); +#ifdef CONFIG_TCPC_VSAFE0V_DETECT_IC + ret = tcpci_is_vsafe0v(tcpc); + if (ret == 0) { + TYPEC_NEW_STATE(typec_water_protection_wait); + typec_wait_ps_change(tcpc, TYPEC_WAIT_PS_SRC_VSAFE0V); + } else { + TYPEC_NEW_STATE(typec_water_protection); + tcpci_set_water_protection(tcpc, true); + } +#else + /* TODO: Wait ps change ? */ +#endif /* CONFIG_TCPC_VSAFE0V_DETECT_IC */ + +out: + tcpci_notify_wd_status(tcpc, wd); + if (tcpc->typec_state == typec_water_protection || + tcpc->typec_state == typec_water_protection_wait) { + typec_alert_attach_state_change(tcpc); + tcpc->typec_attach_old = tcpc->typec_attach_new; + } + return ret; +} +#endif /* CONFIG_WATER_DETECTION */ + +#ifdef CONFIG_CABLE_TYPE_DETECTION +int tcpc_typec_handle_ctd(struct tcpc_device *tcpc, + enum tcpc_cable_type cable_type) +{ + int ret; + + if (!(tcpc->tcpc_flags & TCPC_FLAGS_CABLE_TYPE_DETECTION)) + return 0; + + + /* Filter out initial no cable */ + if (cable_type == TCPC_CABLE_TYPE_C2C) { + ret = tcpci_get_cc(tcpc); + if (ret >= 0) { + if (typec_is_cc_no_res() && + (tcpc->typec_state == typec_unattached_snk || + tcpc->typec_state == typec_unattached_src)) { + TCPC_INFO("%s toggling or open\n", __func__); + cable_type = TCPC_CABLE_TYPE_NONE; + } + } + } + + TCPC_INFO("%s cable (%d, %d)\n", __func__, tcpc->typec_cable_type, + cable_type); + + if (tcpc->typec_cable_type == cable_type) + return 0; + + if (tcpc->typec_cable_type != TCPC_CABLE_TYPE_NONE && + cable_type != TCPC_CABLE_TYPE_NONE) { + TCPC_INFO("%s ctd done once %d\n", __func__, + tcpc->typec_cable_type); + return 0; + } + + tcpc->typec_cable_type = cable_type; + + TCPC_INFO("%s cable type %d\n", __func__, tcpc->typec_cable_type); + tcpci_notify_cable_type(tcpc); + return 0; +} +#endif /* CONFIG_CABLE_TYPE_DETECTION */ diff --git a/drivers/usb/typec/mmi_tcpc/tcpm.c b/drivers/usb/typec/mmi_tcpc/tcpm.c new file mode 100644 index 000000000000..5a8f13e9becb --- /dev/null +++ b/drivers/usb/typec/mmi_tcpc/tcpm.c @@ -0,0 +1,2046 @@ +/* + * Copyright (C) 2020 Richtek Inc. + * + * Power Delivery Managert Driver + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "inc/tcpm.h" +#include "inc/tcpci.h" +#include "inc/tcpci_typec.h" + +#ifdef CONFIG_USB_POWER_DELIVERY +#include "inc/pd_core.h" +#include "inc/pd_dpm_core.h" +#include "pd_dpm_prv.h" +#include "inc/pd_policy_engine.h" +#include "inc/pd_dpm_pdo_select.h" +#endif /* CONFIG_USB_POWER_DELIVERY */ + + +/* Check status */ +static int tcpm_check_typec_attached(struct tcpc_device *tcpc) +{ + if (tcpc->typec_attach_old == TYPEC_UNATTACHED || + tcpc->typec_attach_new == TYPEC_UNATTACHED) + return TCPM_ERROR_UNATTACHED; + + return 0; +} + +#ifdef CONFIG_USB_POWER_DELIVERY +static int tcpm_check_pd_attached(struct tcpc_device *tcpc) +{ + int ret = TCPM_SUCCESS; + struct pd_port *pd_port = &tcpc->pd_port; + struct pe_data *pe_data = &pd_port->pe_data; + + tcpci_lock_typec(tcpc); + + if (!tcpc->pd_inited_flag) { + ret = TCPM_ERROR_PE_NOT_READY; + goto unlock_typec_out; + } + + ret = tcpm_check_typec_attached(tcpc); + if (ret != TCPM_SUCCESS) + goto unlock_typec_out; + +#ifdef CONFIG_TYPEC_CAP_CUSTOM_SRC + if (tcpc->typec_attach_old == TYPEC_ATTACHED_CUSTOM_SRC) + ret = TCPM_ERROR_CUSTOM_SRC; +#endif /* CONFIG_TYPEC_CAP_CUSTOM_SRC */ + +unlock_typec_out: + tcpci_unlock_typec(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + mutex_lock(&pd_port->pd_lock); + + if (!pe_data->pd_prev_connected) { + ret = TCPM_ERROR_NO_PD_CONNECTED; + goto unlock_pd_out; + } + + if (!pe_data->pe_ready) + ret = TCPM_ERROR_PE_NOT_READY; + +unlock_pd_out: + mutex_unlock(&pd_port->pd_lock); + return ret; +} +#endif /* CONFIG_USB_POWER_DELIVERY */ + + +/* Inquire TCPC status */ + +int tcpm_shutdown(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TCPC_SHUTDOWN_VBUS_DISABLE + if (tcpc->typec_power_ctrl) + tcpci_disable_vbus_control(tcpc); +#endif /* CONFIG_TCPC_SHUTDOWN_VBUS_DISABLE */ + + if (tcpc->ops->deinit) + tcpc->ops->deinit(tcpc); + + return 0; +} +EXPORT_SYMBOL(tcpm_shutdown); + +int tcpm_inquire_remote_cc(struct tcpc_device *tcpc, + uint8_t *cc1, uint8_t *cc2, bool from_ic) +{ + int rv = 0; + + tcpci_lock_typec(tcpc); + if (from_ic) { + rv = tcpci_get_cc(tcpc); + if (rv < 0) + goto out; + } + + *cc1 = tcpc->typec_remote_cc[0]; + *cc2 = tcpc->typec_remote_cc[1]; +out: + tcpci_unlock_typec(tcpc); + return rv; +} + +int tcpm_inquire_typec_remote_rp_curr(struct tcpc_device *tcpc) +{ + int rp_lvl, ret = 0; + + if (tcpm_check_typec_attached(tcpc)) + return 0; + + rp_lvl = tcpc->typec_remote_rp_level; + switch (rp_lvl) { + case TYPEC_CC_VOLT_SNK_DFT: + ret = 500; + break; + case TYPEC_CC_VOLT_SNK_1_5: + ret = 1500; + break; + case TYPEC_CC_VOLT_SNK_3_0: + ret = 3000; + break; + default: + break; + } + + return ret; +} + +int tcpm_inquire_vbus_level( + struct tcpc_device *tcpc, bool from_ic) +{ + int rv = 0; + uint16_t power_status = 0; + + if (from_ic) { + rv = tcpci_get_power_status(tcpc, &power_status); + if (rv < 0) + return rv; + } + + return tcpc->vbus_level; +} + +bool tcpm_inquire_cc_polarity( + struct tcpc_device *tcpc) +{ + return tcpc->typec_polarity; +} +EXPORT_SYMBOL(tcpm_inquire_cc_polarity); + +uint8_t tcpm_inquire_typec_attach_state( + struct tcpc_device *tcpc) +{ + return tcpc->typec_attach_new; +} + +uint8_t tcpm_inquire_typec_role( + struct tcpc_device *tcpc) +{ + return tcpc->typec_role; +} +EXPORT_SYMBOL(tcpm_inquire_typec_role); + +uint8_t tcpm_inquire_typec_local_rp( + struct tcpc_device *tcpc) +{ + uint8_t level; + + switch (tcpc->typec_local_rp_level) { + case TYPEC_CC_RP_1_5: + level = 1; + break; + case TYPEC_CC_RP_3_0: + level = 2; + break; + default: + case TYPEC_CC_RP_DFT: + level = 0; + break; + } + + return level; +} + +int tcpm_typec_set_wake_lock( + struct tcpc_device *tcpc, bool user_lock) +{ + int ret; + + mutex_lock(&tcpc->access_lock); + ret = tcpci_set_wake_lock( + tcpc, tcpc->wake_lock_pd, user_lock); + tcpc->wake_lock_user = user_lock; + mutex_unlock(&tcpc->access_lock); + + return ret; +} + +int tcpm_typec_set_usb_sink_curr( + struct tcpc_device *tcpc, int curr) +{ + bool force_sink_vbus = true; + +#ifdef CONFIG_USB_POWER_DELIVERY + struct pd_port *pd_port = &tcpc->pd_port; + + if (pd_port->pe_data.pd_prev_connected) + force_sink_vbus = false; +#endif /* CONFIG_USB_POWER_DELIVERY */ + + tcpci_lock_typec(tcpc); + tcpc->typec_usb_sink_curr = curr; + + if (tcpc->typec_remote_rp_level != TYPEC_CC_VOLT_SNK_DFT) + force_sink_vbus = false; + + if (force_sink_vbus) { + tcpci_sink_vbus(tcpc, + TCP_VBUS_CTRL_TYPEC, TCPC_VBUS_SINK_5V, -1); + } + tcpci_unlock_typec(tcpc); + + return 0; +} + +int tcpm_typec_set_rp_level( + struct tcpc_device *tcpc, uint8_t level) +{ + int ret = 0; + uint8_t res; + + if (level == 2) + res = TYPEC_CC_RP_3_0; + else if (level == 1) + res = TYPEC_CC_RP_1_5; + else + res = TYPEC_CC_RP_DFT; + + tcpci_lock_typec(tcpc); + ret = tcpc_typec_set_rp_level(tcpc, res); + tcpci_unlock_typec(tcpc); + + return ret; +} + +int tcpm_typec_set_custom_hv(struct tcpc_device *tcpc, bool en) +{ +#ifdef CONFIG_TYPEC_CAP_CUSTOM_HV + int ret = tcpm_check_typec_attached(tcpc); + + if (ret != TCPM_SUCCESS) + return ret; + + tcpci_lock_typec(tcpc); + tcpc->typec_during_custom_hv = en; + tcpci_unlock_typec(tcpc); + + return ret; +#else + return TCPM_ERROR_NO_SUPPORT; +#endif /* CONFIG_TYPEC_CAP_CUSTOM_HV */ +} + +int tcpm_typec_role_swap(struct tcpc_device *tcpc) +{ +#ifdef CONFIG_TYPEC_CAP_ROLE_SWAP + int ret = tcpm_check_typec_attached(tcpc); + + if (ret != TCPM_SUCCESS) + return ret; + + tcpci_lock_typec(tcpc); + ret = tcpc_typec_swap_role(tcpc); + tcpci_unlock_typec(tcpc); + + return ret; +#else + return TCPM_ERROR_NO_SUPPORT; +#endif /* CONFIG_TYPEC_CAP_ROLE_SWAP */ +} +EXPORT_SYMBOL(tcpm_typec_role_swap); + +int tcpm_typec_change_role( + struct tcpc_device *tcpc, uint8_t typec_role) +{ + int ret = 0; + + tcpci_lock_typec(tcpc); + ret = tcpc_typec_change_role(tcpc, typec_role, false); + tcpci_unlock_typec(tcpc); + + return ret; +} +EXPORT_SYMBOL(tcpm_typec_change_role); + +/* @postpone: whether to postpone Type-C role change until unattached */ +int tcpm_typec_change_role_postpone( + struct tcpc_device *tcpc, uint8_t typec_role, bool postpone) +{ + int ret = 0; + + tcpci_lock_typec(tcpc); + ret = tcpc_typec_change_role(tcpc, typec_role, postpone); + tcpci_unlock_typec(tcpc); + + return ret; +} +EXPORT_SYMBOL(tcpm_typec_change_role_postpone); + +int tcpm_typec_error_recovery(struct tcpc_device *tcpc) +{ + int ret = 0; + + tcpci_lock_typec(tcpc); + ret = tcpc_typec_error_recovery(tcpc); + tcpci_unlock_typec(tcpc); + + return ret; +} + +int tcpm_typec_disable_function( + struct tcpc_device *tcpc, bool disable) +{ + int ret = 0; + + tcpci_lock_typec(tcpc); + ret = (disable ? tcpc_typec_disable : tcpc_typec_enable)(tcpc); + tcpci_unlock_typec(tcpc); + + return ret; +} + +#ifdef CONFIG_USB_POWER_DELIVERY + +bool tcpm_inquire_pd_connected( + struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->pe_data.pd_connected; +} + +bool tcpm_inquire_pd_prev_connected( + struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->pe_data.pd_prev_connected; +} + +uint8_t tcpm_inquire_pd_data_role( + struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->data_role; +} +EXPORT_SYMBOL(tcpm_inquire_pd_data_role); + +uint8_t tcpm_inquire_pd_power_role( + struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->power_role; +} +EXPORT_SYMBOL(tcpm_inquire_pd_power_role); + +uint8_t tcpm_inquire_pd_vconn_role( + struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->vconn_role; +} +EXPORT_SYMBOL(tcpm_inquire_pd_vconn_role); + +uint8_t tcpm_inquire_pd_pe_ready( + struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->pe_data.pe_ready; +} + +uint8_t tcpm_inquire_cable_current( + struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + if (pd_port->pe_data.power_cable_present) + return pd_get_cable_curr_lvl(pd_port)+1; + + return PD_CABLE_CURR_UNKNOWN; +} + +uint32_t tcpm_inquire_dpm_flags(struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->pe_data.dpm_flags; +} +EXPORT_SYMBOL(tcpm_inquire_dpm_flags); + +uint32_t tcpm_inquire_dpm_caps(struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->dpm_caps; +} + +void tcpm_set_dpm_caps(struct tcpc_device *tcpc, uint32_t caps) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + mutex_lock(&pd_port->pd_lock); + pd_port->dpm_caps = caps; + mutex_unlock(&pd_port->pd_lock); +} + +/* Inquire TCPC to get PD Information */ + +int tcpm_inquire_pd_contract( + struct tcpc_device *tcpc, int *mv, int *ma) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + if ((mv == NULL) || (ma == NULL)) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + if (pd_port->pe_data.explicit_contract) { + *mv = pd_port->request_v; + *ma = pd_port->request_i; + } else + ret = TCPM_ERROR_NO_EXPLICIT_CONTRACT; + mutex_unlock(&pd_port->pd_lock); + + return ret; + +} + +int tcpm_inquire_cable_inform( + struct tcpc_device *tcpc, uint32_t *vdos) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + if (vdos == NULL) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + if (pd_port->pe_data.power_cable_present) { + memcpy(vdos, pd_port->pe_data.cable_vdos, + sizeof(uint32_t) * VDO_MAX_NR); + } else + ret = TCPM_ERROR_NO_POWER_CABLE; + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + +int tcpm_inquire_pd_partner_inform( + struct tcpc_device *tcpc, uint32_t *vdos) +{ +#ifdef CONFIG_USB_PD_KEEP_PARTNER_ID + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + if (vdos == NULL) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + if (pd_port->pe_data.partner_id_present) { + memcpy(vdos, pd_port->pe_data.partner_vdos, + sizeof(uint32_t) * VDO_MAX_NR); + } else + ret = TCPM_ERROR_NO_PARTNER_INFORM; + mutex_unlock(&pd_port->pd_lock); + + return ret; +#else + return TCPM_ERROR_NO_SUPPORT; +#endif /* CONFIG_USB_PD_KEEP_PARTNER_ID */ +} +EXPORT_SYMBOL(tcpm_inquire_pd_partner_inform); + +int tcpm_inquire_pd_partner_svids( + struct tcpc_device *tcpc, struct tcpm_svid_list *list) +{ +#ifdef CONFIG_USB_PD_KEEP_SVIDS + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + struct svdm_svid_list *svdm_list = &pd_port->pe_data.remote_svid_list; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + if (list == NULL) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + if (svdm_list->cnt) { + list->cnt = svdm_list->cnt; + memcpy(list->svids, svdm_list->svids, + sizeof(uint16_t) * svdm_list->cnt); + } else + ret = TCPM_ERROR_NO_PARTNER_INFORM; + mutex_unlock(&pd_port->pd_lock); + + return ret; +#else + return TCPM_ERROR_NO_SUPPORT; +#endif /* CONFIG_USB_PD_KEEP_SVIDS */ +} + +int tcpm_inquire_pd_partner_modes( + struct tcpc_device *tcpc, uint16_t svid, struct tcpm_mode_list *list) +{ +#ifdef CONFIG_USB_PD_ALT_MODE + int ret = TCPM_SUCCESS; + struct svdm_svid_data *svid_data = NULL; + struct pd_port *pd_port = &tcpc->pd_port; + + mutex_lock(&pd_port->pd_lock); + + svid_data = + dpm_get_svdm_svid_data(pd_port, USB_SID_DISPLAYPORT); + + if (svid_data == NULL) { + mutex_unlock(&pd_port->pd_lock); + return TCPM_ERROR_PARAMETER; + } + + if (svid_data->remote_mode.mode_cnt) { + list->cnt = svid_data->remote_mode.mode_cnt; + memcpy(list->modes, svid_data->remote_mode.mode_vdo, + sizeof(uint32_t) * list->cnt); + } else + ret = TCPM_ERROR_NO_PARTNER_INFORM; + mutex_unlock(&pd_port->pd_lock); + + return ret; +#else + return TCPM_ERROR_NO_SUPPORT; +#endif /* CONFIG_USB_PD_KEEP_SVIDS */ +} + +int tcpm_inquire_pd_source_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap *cap) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + if (cap == NULL) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + if (pd_port->pe_data.remote_src_cap.nr) { + cap->cnt = pd_port->pe_data.remote_src_cap.nr; + memcpy(cap->pdos, pd_port->pe_data.remote_src_cap.pdos, + sizeof(uint32_t) * cap->cnt); + } else + ret = TCPM_ERROR_NO_SOURCE_CAP; + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + +int tcpm_inquire_pd_sink_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap *cap) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + if (cap == NULL) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + if (pd_port->pe_data.remote_snk_cap.nr) { + cap->cnt = pd_port->pe_data.remote_snk_cap.nr; + memcpy(cap->pdos, pd_port->pe_data.remote_snk_cap.pdos, + sizeof(uint32_t) * cap->cnt); + } else + ret = TCPM_ERROR_NO_SINK_CAP; + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + +bool tcpm_extract_power_cap_val( + uint32_t pdo, struct tcpm_power_cap_val *cap) +{ + struct dpm_pdo_info_t info; + + dpm_extract_pdo_info(pdo, &info); + + cap->type = info.type; + cap->min_mv = info.vmin; + cap->max_mv = info.vmax; + + if (info.type == DPM_PDO_TYPE_BAT) + cap->uw = info.uw; + else + cap->ma = info.ma; + +#ifdef CONFIG_USB_PD_REV30 + if (info.type == DPM_PDO_TYPE_APDO) { + cap->apdo_type = info.apdo_type; + cap->pwr_limit = info.pwr_limit; + } +#endif /* CONFIG_USB_PD_REV30 */ + + return cap->type != TCPM_POWER_CAP_VAL_TYPE_UNKNOWN; +} + +extern bool tcpm_extract_power_cap_list( + struct tcpm_power_cap *cap, struct tcpm_power_cap_list *cap_list) +{ + uint8_t i; + + cap_list->nr = cap->cnt; + for (i = 0; i < cap_list->nr; i++) { + if (!tcpm_extract_power_cap_val( + cap->pdos[i], &cap_list->cap_val[i])) + return false; + } + + return true; +} + +int tcpm_get_remote_power_cap(struct tcpc_device *tcpc, + struct tcpm_remote_power_cap *remote_cap) +{ + struct pd_port *pd_port = &tcpc->pd_port; + struct tcpm_power_cap_val cap; + int i; + + mutex_lock(&pd_port->pd_lock); + remote_cap->selected_cap_idx = + tcpc->pd_port.pe_data.remote_selected_cap; + remote_cap->nr = tcpc->pd_port.pe_data.remote_src_cap.nr; + for (i = 0; i < remote_cap->nr; i++) { + tcpm_extract_power_cap_val( + tcpc->pd_port.pe_data.remote_src_cap.pdos[i], &cap); + remote_cap->max_mv[i] = cap.max_mv; + remote_cap->min_mv[i] = cap.min_mv; + remote_cap->ma[i] = cap.ma; + remote_cap->type[i] = cap.type; + } + mutex_unlock(&pd_port->pd_lock); + + return TCPM_SUCCESS; +} + +int tcpm_set_remote_power_cap(struct tcpc_device *tcpc, + int mv, int ma) +{ + return tcpm_dpm_pd_request(tcpc, mv, ma, NULL); +} + +static inline int __tcpm_inquire_select_source_cap( + struct pd_port *pd_port, struct tcpm_power_cap_val *cap_val) +{ + uint8_t sel; + struct pe_data *pe_data = &pd_port->pe_data; + + if (pd_port->power_role != PD_ROLE_SINK) + return TCPM_ERROR_POWER_ROLE; + + sel = RDO_POS(pd_port->last_rdo) - 1; + if (sel > pe_data->remote_src_cap.nr) + return TCPM_ERROR_NO_SOURCE_CAP; + + if (!tcpm_extract_power_cap_val( + pe_data->remote_src_cap.pdos[sel], cap_val)) + return TCPM_ERROR_NOT_FOUND; + + return TCPM_SUCCESS; +} + +int tcpm_inquire_select_source_cap( + struct tcpc_device *tcpc, struct tcpm_power_cap_val *cap_val) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + if (cap_val == NULL) + return TCPM_ERROR_PARAMETER; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + mutex_lock(&pd_port->pd_lock); + ret = __tcpm_inquire_select_source_cap(pd_port, cap_val); + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + + +/* Request TCPC to send PD Request */ + +#ifdef CONFIG_USB_PD_BLOCK_TCPM + +#define TCPM_BK_PD_CMD_TOUT 500 + +/* tPSTransition 550 ms */ +#define TCPM_BK_REQUEST_TOUT 1500 + +/* tPSSourceOff 920 ms, tPSSourceOn 480 ms*/ +#define TCPM_BK_PR_SWAP_TOUT 2500 +#define TCPM_BK_HARD_RESET_TOUT 3500 + +static int tcpm_put_tcp_dpm_event_bk( + struct tcpc_device *tcpc, struct tcp_dpm_event *event, + uint32_t tout_ms, uint8_t *data, uint8_t size); +#endif /* CONFIG_USB_PD_BLOCK_TCPM */ + +int tcpm_put_tcp_dpm_event_cb(struct tcpc_device *tcpc, + struct tcp_dpm_event *event, + const struct tcp_dpm_event_cb_data *cb_data) +{ + event->user_data = cb_data->user_data; + event->event_cb = cb_data->event_cb; + + return tcpm_put_tcp_dpm_event(tcpc, event); +} + +static int tcpm_put_tcp_dpm_event_cbk1(struct tcpc_device *tcpc, + struct tcp_dpm_event *event, + const struct tcp_dpm_event_cb_data *cb_data, uint32_t tout_ms) +{ +#ifdef CONFIG_USB_PD_BLOCK_TCPM + if (cb_data == NULL) { + return tcpm_put_tcp_dpm_event_bk( + tcpc, event, tout_ms, NULL, 0); + } +#endif /* CONFIG_USB_PD_BLOCK_TCPM */ + + return tcpm_put_tcp_dpm_event_cb(tcpc, event, cb_data); +} + +int tcpm_dpm_pd_power_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_PR_SWAP_AS_SNK + role, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PR_SWAP_TOUT); +} +EXPORT_SYMBOL(tcpm_dpm_pd_power_swap); + +int tcpm_dpm_pd_data_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DR_SWAP_AS_UFP + role, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} +EXPORT_SYMBOL(tcpm_dpm_pd_data_swap); + +int tcpm_dpm_pd_vconn_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_VCONN_SWAP_OFF + role, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} +EXPORT_SYMBOL(tcpm_dpm_pd_vconn_swap); + +int tcpm_dpm_pd_goto_min(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GOTOMIN, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_pd_soft_reset(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_SOFTRESET, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_REQUEST_TOUT); +} + +int tcpm_dpm_pd_get_source_cap(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_SOURCE_CAP, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_REQUEST_TOUT); +} + +int tcpm_dpm_pd_get_sink_cap(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_SINK_CAP, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_pd_request(struct tcpc_device *tcpc, + int mv, int ma, const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_REQUEST, + .tcp_dpm_data.pd_req.mv = mv, + .tcp_dpm_data.pd_req.ma = ma, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_REQUEST_TOUT); +} + +int tcpm_dpm_pd_request_ex(struct tcpc_device *tcpc, + uint8_t pos, uint32_t max, uint32_t oper, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_REQUEST_EX, + .tcp_dpm_data.pd_req_ex.pos = pos, + .tcp_dpm_data.pd_req_ex.max = max, + .tcp_dpm_data.pd_req_ex.oper = oper, + }; + + if (oper > max) + return TCPM_ERROR_PARAMETER; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_REQUEST_TOUT); +} + +int tcpm_dpm_pd_bist_cm2(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_BIST_CM2, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +#ifdef CONFIG_USB_PD_REV30 + +static int tcpm_put_tcp_dpm_event_cbk2(struct tcpc_device *tcpc, + struct tcp_dpm_event *event, + const struct tcp_dpm_event_cb_data *cb_data, + uint32_t tout_ms, uint8_t *data, uint8_t size) +{ +#ifdef CONFIG_USB_PD_BLOCK_TCPM + if (cb_data == NULL) { + return tcpm_put_tcp_dpm_event_bk( + tcpc, event, tout_ms, data, size); + } +#endif /* CONFIG_USB_PD_BLOCK_TCPM */ + + return tcpm_put_tcp_dpm_event_cb(tcpc, event, cb_data); +} + +int tcpm_dpm_pd_get_source_cap_ext(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data, + struct pd_source_cap_ext *src_cap_ext) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_SOURCE_CAP_EXT, + }; + + return tcpm_put_tcp_dpm_event_cbk2( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT, + (uint8_t *) src_cap_ext, PD_SCEDB_SIZE); +} + +int tcpm_dpm_pd_fast_swap(struct tcpc_device *tcpc, + uint8_t role, const struct tcp_dpm_event_cb_data *cb_data) +{ + return TCPM_ERROR_NO_SUPPORT; +} + +int tcpm_dpm_pd_get_status(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data, struct pd_status *status) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_STATUS, + }; + + return tcpm_put_tcp_dpm_event_cbk2( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT, + (uint8_t *) status, PD_SDB_SIZE); +} + +int tcpm_dpm_pd_get_pps_status_raw(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data, + struct pd_pps_status_raw *pps_status) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_PPS_STATUS, + }; + + return tcpm_put_tcp_dpm_event_cbk2( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT, + (uint8_t *) pps_status, PD_PPSSDB_SIZE); +} + +int tcpm_dpm_pd_get_pps_status(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data, + struct pd_pps_status *pps_status) +{ + int ret; + struct pd_pps_status_raw pps_status_raw; + + ret = tcpm_dpm_pd_get_pps_status_raw( + tcpc, cb_data, &pps_status_raw); + + if (ret != 0) + return ret; + + if (pps_status_raw.output_vol_raw == 0xffff) + pps_status->output_mv = -1; + else + pps_status->output_mv = + PD_PPS_GET_OUTPUT_MV(pps_status_raw.output_vol_raw); + + if (pps_status_raw.output_curr_raw == 0xff) + pps_status->output_ma = -1; + else + pps_status->output_ma = + PD_PPS_GET_OUTPUT_MA(pps_status_raw.output_curr_raw); + + pps_status->real_time_flags = pps_status_raw.real_time_flags; + return ret; +} + +int tcpm_dpm_pd_get_country_code(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data, + struct pd_country_codes *ccdb) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_COUNTRY_CODE, + }; + + return tcpm_put_tcp_dpm_event_cbk2( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT, + (uint8_t *) ccdb, PD_CCDB_MAX_SIZE); +} + +int tcpm_dpm_pd_get_country_info(struct tcpc_device *tcpc, uint32_t ccdo, + const struct tcp_dpm_event_cb_data *cb_data, + struct pd_country_info *cidb) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_COUNTRY_INFO, + .tcp_dpm_data.index = ccdo, + }; + + return tcpm_put_tcp_dpm_event_cbk2( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT, + (uint8_t *) cidb, PD_CIDB_MAX_SIZE); +} + +int tcpm_dpm_pd_get_bat_cap(struct tcpc_device *tcpc, + struct pd_get_battery_capabilities *gbcdb, + const struct tcp_dpm_event_cb_data *cb_data, + struct pd_battery_capabilities *bcdb) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_BAT_CAP, + .tcp_dpm_data.gbcdb = *gbcdb, + }; + + return tcpm_put_tcp_dpm_event_cbk2( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT, + (uint8_t *) bcdb, PD_BCDB_SIZE); +} + +int tcpm_dpm_pd_get_bat_status(struct tcpc_device *tcpc, + struct pd_get_battery_status *gbsdb, + const struct tcp_dpm_event_cb_data *cb_data, + uint32_t *bsdo) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_BAT_STATUS, + .tcp_dpm_data.gbsdb = *gbsdb, + }; + + return tcpm_put_tcp_dpm_event_cbk2( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT, + (uint8_t *) bsdo, sizeof(uint32_t) * PD_BSDO_SIZE); +} + +int tcpm_dpm_pd_get_mfrs_info(struct tcpc_device *tcpc, + struct pd_get_manufacturer_info *gmidb, + const struct tcp_dpm_event_cb_data *cb_data, + struct pd_manufacturer_info *midb) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_GET_MFRS_INFO, + .tcp_dpm_data.gmidb = *gmidb, + }; + + return tcpm_put_tcp_dpm_event_cbk2( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT, + (uint8_t *) midb, PD_MIDB_MAX_SIZE); +} + +int tcpm_dpm_pd_alert(struct tcpc_device *tcpc, + uint32_t ado, const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_ALERT, + .tcp_dpm_data.index = ado, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +#endif /* CONFIG_USB_PD_REV30 */ + +int tcpm_dpm_pd_hard_reset(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_HARD_RESET, + }; + + /* check not block case !!! */ + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_HARD_RESET_TOUT); +} + +int tcpm_dpm_pd_error_recovery(struct tcpc_device *tcpc) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_ERROR_RECOVERY, + }; + + if (tcpm_put_tcp_dpm_event(tcpc, &tcp_event) != TCPM_SUCCESS) + tcpm_typec_error_recovery(tcpc); + + return TCPM_SUCCESS; +} + +int tcpm_dpm_pd_cable_soft_reset(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_CABLE_SOFTRESET, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_vdm_discover_cable(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DISCOVER_CABLE, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_vdm_discover_id(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DISCOVER_ID, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_vdm_discover_svid(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DISCOVER_SVIDS, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_vdm_discover_mode(struct tcpc_device *tcpc, + uint16_t svid, const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DISCOVER_MODES, + .tcp_dpm_data.svdm_data.svid = svid, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_vdm_enter_mode(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_ENTER_MODE, + .tcp_dpm_data.svdm_data.svid = svid, + .tcp_dpm_data.svdm_data.ops = ops, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_vdm_exit_mode(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_EXIT_MODE, + .tcp_dpm_data.svdm_data.svid = svid, + .tcp_dpm_data.svdm_data.ops = ops, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_vdm_attention(struct tcpc_device *tcpc, + uint16_t svid, uint8_t ops, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_ATTENTION, + .tcp_dpm_data.svdm_data.svid = svid, + .tcp_dpm_data.svdm_data.ops = ops, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +#ifdef CONFIG_USB_PD_ALT_MODE + +int tcpm_inquire_dp_ufp_u_state( + struct tcpc_device *tcpc, uint8_t *state) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + if (state == NULL) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + *state = pd_get_dp_data(pd_port)->ufp_u_state; + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + +int tcpm_dpm_dp_attention(struct tcpc_device *tcpc, + uint32_t dp_status, uint32_t mask, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DP_ATTENTION, + .tcp_dpm_data.dp_data.val = dp_status, + .tcp_dpm_data.dp_data.mask = mask, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + +int tcpm_inquire_dp_dfp_u_state( + struct tcpc_device *tcpc, uint8_t *state) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS) + return ret; + + if (state == NULL) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + *state = pd_get_dp_data(pd_port)->dfp_u_state; + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + +int tcpm_dpm_dp_status_update(struct tcpc_device *tcpc, + uint32_t dp_status, uint32_t mask, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DP_STATUS_UPDATE, + .tcp_dpm_data.dp_data.val = dp_status, + .tcp_dpm_data.dp_data.mask = mask, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +int tcpm_dpm_dp_config(struct tcpc_device *tcpc, + uint32_t dp_config, uint32_t mask, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DP_CONFIG, + .tcp_dpm_data.dp_data.val = dp_config, + .tcp_dpm_data.dp_data.mask = mask, + }; + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); +} + +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifdef CONFIG_USB_PD_CUSTOM_VDM + +int tcpm_dpm_send_custom_vdm( + struct tcpc_device *tcpc, + struct tcp_dpm_custom_vdm_data *vdm_data, + const struct tcp_dpm_event_cb_data *cb_data) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_UVDM, + }; + + if (vdm_data->cnt > PD_DATA_OBJ_SIZE) + return TCPM_ERROR_PARAMETER; + + memcpy(&tcp_event.tcp_dpm_data.vdm_data, + vdm_data, sizeof(struct tcp_dpm_custom_vdm_data)); + + /* Check it later */ + ret = tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_PD_CMD_TOUT); + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + if ((ret == TCP_DPM_RET_SUCCESS) + && (cb_data == NULL) && vdm_data->wait_resp) { + mutex_lock(&pd_port->pd_lock); + vdm_data->cnt = tcpc->pd_port.uvdm_cnt; + memcpy(vdm_data->vdos, + tcpc->pd_port.uvdm_data, + sizeof(uint32_t) * vdm_data->cnt); + mutex_unlock(&pd_port->pd_lock); + } +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + + return ret; +} +#endif /* CONFIG_USB_PD_CUSTOM_VDM */ + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND +void tcpm_replace_curr_tcp_event( + struct pd_port *pd_port, struct tcp_dpm_event *event) +{ + int reason = TCP_DPM_RET_DENIED_UNKNOWN; + + switch (event->event_id) { + case TCP_DPM_EVT_HARD_RESET: + reason = TCP_DPM_RET_DROP_SENT_HRESET; + break; + case TCP_DPM_EVT_ERROR_RECOVERY: + reason = TCP_DPM_RET_DROP_ERROR_REOCVERY; + break; + } + + mutex_lock(&pd_port->pd_lock); + pd_notify_tcp_event_2nd_result(pd_port, reason); + pd_port->tcp_event_drop_reset_once = true; + pd_port->tcp_event_id_2nd = event->event_id; + memcpy(&pd_port->tcp_event, event, sizeof(struct tcp_dpm_event)); + mutex_unlock(&pd_port->pd_lock); +} +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + +int tcpm_put_tcp_dpm_event( + struct tcpc_device *tcpc, struct tcp_dpm_event *event) +{ + int ret; + bool imme = event->event_id >= TCP_DPM_EVT_IMMEDIATELY; + struct pd_port *pd_port = &tcpc->pd_port; + + ret = tcpm_check_pd_attached(tcpc); + if (ret != TCPM_SUCCESS && + (imme && ret == TCPM_ERROR_UNATTACHED)) + return ret; + + if (imme) { + ret = pd_put_tcp_pd_event(pd_port, event->event_id); + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + if (ret) + tcpm_replace_curr_tcp_event(pd_port, event); +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + } else + ret = pd_put_deferred_tcp_event(tcpc, event); + + if (!ret) + return TCPM_ERROR_PUT_EVENT; + + return TCPM_SUCCESS; +} + +int tcpm_notify_vbus_stable( + struct tcpc_device *tcpc) +{ +#if CONFIG_USB_PD_VBUS_STABLE_TOUT + tcpc_disable_timer(tcpc, PD_TIMER_VBUS_STABLE); +#endif + + pd_put_vbus_stable_event(tcpc); + return TCPM_SUCCESS; +} + +uint8_t tcpm_inquire_pd_charging_policy(struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->dpm_charging_policy; +} + +uint8_t tcpm_inquire_pd_charging_policy_default(struct tcpc_device *tcpc) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + return pd_port->dpm_charging_policy_default; +} + +int tcpm_reset_pd_charging_policy(struct tcpc_device *tcpc, + const struct tcp_dpm_event_cb_data *cb_data) +{ + return tcpm_set_pd_charging_policy( + tcpc, tcpc->pd_port.dpm_charging_policy_default, cb_data); +} + +int tcpm_set_pd_charging_policy_default( + struct tcpc_device *tcpc, uint8_t policy) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + /* PPS should not be default charging policy ... */ + if ((policy & DPM_CHARGING_POLICY_MASK) >= DPM_CHARGING_POLICY_PPS) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + pd_port->dpm_charging_policy_default = policy; + mutex_unlock(&pd_port->pd_lock); + + return TCPM_SUCCESS; +} + +int tcpm_set_pd_charging_policy(struct tcpc_device *tcpc, + uint8_t policy, const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_REQUEST_AGAIN, + }; + + struct pd_port *pd_port = &tcpc->pd_port; + + /* PPS should call another function ... */ + if ((policy & DPM_CHARGING_POLICY_MASK) >= DPM_CHARGING_POLICY_PPS) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + if (pd_port->dpm_charging_policy == policy) { + mutex_unlock(&pd_port->pd_lock); + return TCPM_SUCCESS; + } + + pd_port->dpm_charging_policy = policy; + mutex_unlock(&pd_port->pd_lock); + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_REQUEST_TOUT); +} + +#ifdef CONFIG_USB_PD_DIRECT_CHARGE +int tcpm_set_direct_charge_en(struct tcpc_device *tcpc, bool en) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + mutex_lock(&pd_port->pd_lock); + tcpc->pd_during_direct_charge = en; + mutex_unlock(&pd_port->pd_lock); + + return 0; +} + +bool tcpm_inquire_during_direct_charge(struct tcpc_device *tcpc) +{ + return tcpc->pd_during_direct_charge; +} +#endif /* CONFIG_USB_PD_DIRECT_CHARGE */ + +static int tcpm_put_tcp_dummy_event(struct tcpc_device *tcpc) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_DUMMY, + }; + + return tcpm_put_tcp_dpm_event(tcpc, &tcp_event); +} + +#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE + +int tcpm_dpm_set_vconn_supply_mode(struct tcpc_device *tcpc, uint8_t mode) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + mutex_lock(&pd_port->pd_lock); + tcpc->tcpc_vconn_supply = mode; + dpm_reaction_set(pd_port, + DPM_REACTION_DYNAMIC_VCONN | + DPM_REACTION_VCONN_STABLE_DELAY); + mutex_unlock(&pd_port->pd_lock); + + return tcpm_put_tcp_dummy_event(tcpc); +} + +#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */ + +#ifdef CONFIG_USB_PD_REV30_PPS_SINK + +int tcpm_set_apdo_charging_policy(struct tcpc_device *tcpc, + uint8_t policy, int mv, int ma, + const struct tcp_dpm_event_cb_data *cb_data) +{ + struct tcp_dpm_event tcp_event = { + .event_id = TCP_DPM_EVT_REQUEST_AGAIN, + }; + + struct pd_port *pd_port = &tcpc->pd_port; + + /* Not PPS should call another function ... */ + if ((policy & DPM_CHARGING_POLICY_MASK) < DPM_CHARGING_POLICY_PPS) + return TCPM_ERROR_PARAMETER; + + mutex_lock(&pd_port->pd_lock); + if (pd_port->dpm_charging_policy == policy) { + mutex_unlock(&pd_port->pd_lock); + TCPC_INFO("BUG!!! FIX IT!!!\n"); + return tcpm_dpm_pd_request(tcpc, mv, ma, NULL); + /* return TCPM_ERROR_REPEAT_POLICY; */ + } + + if (pd_port->pd_connect_state != PD_CONNECT_PE_READY_SNK_APDO) { + mutex_unlock(&pd_port->pd_lock); + return TCPM_ERROR_INVALID_POLICY; + } + + pd_port->dpm_charging_policy = policy; + pd_port->request_v_apdo = mv; + pd_port->request_i_apdo = ma; + mutex_unlock(&pd_port->pd_lock); + + return tcpm_put_tcp_dpm_event_cbk1( + tcpc, &tcp_event, cb_data, TCPM_BK_REQUEST_TOUT); +} + +int tcpm_inquire_pd_source_apdo(struct tcpc_device *tcpc, + uint8_t apdo_type, uint8_t *cap_i, struct tcpm_power_cap_val *cap_val) +{ + int ret; + uint8_t i; + struct tcpm_power_cap cap; + + ret = tcpm_inquire_pd_source_cap(tcpc, &cap); + if (ret != TCPM_SUCCESS) + return ret; + + for (i = *cap_i; i < cap.cnt; i++) { + if (!tcpm_extract_power_cap_val(cap.pdos[i], cap_val)) + continue; + + if (cap_val->type != DPM_PDO_TYPE_APDO) + continue; + + if ((cap_val->apdo_type & TCPM_APDO_TYPE_MASK) != apdo_type) + continue; + + *cap_i = i+1; + return TCPM_SUCCESS; + } + + return TCPM_ERROR_NOT_FOUND; +} + +bool tcpm_inquire_during_pps_charge(struct tcpc_device *tcpc) +{ + int ret; + struct tcpm_power_cap_val cap = {0}; + + ret = tcpm_inquire_select_source_cap(tcpc, &cap); + if (ret != 0) + return false; + + return (cap.type == TCPM_POWER_CAP_VAL_TYPE_AUGMENT); +} + +#endif /* CONFIG_USB_PD_REV30_PPS_SINK */ + +#ifdef CONFIG_USB_PD_REV30_BAT_INFO + +static void tcpm_alert_bat_changed( + struct pd_port *pd_port, enum pd_battery_reference ref) +{ +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + uint8_t fixed = 0, swap = 0; + + if (ref >= PD_BAT_REF_SWAP0) + swap = 1 << (ref - PD_BAT_REF_SWAP0); + else + fixed = 1 << (ref - PD_BAT_REF_FIXED0); + + pd_port->pe_data.local_alert |= + ADO(ADO_ALERT_BAT_CHANGED, fixed, swap); +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ +} + +static inline uint16_t tcpm_calc_battery_cap( + struct pd_battery_info *battery_info, uint16_t soc) +{ + uint16_t wh; + struct pd_battery_capabilities *bat_cap; + + bat_cap = &battery_info->bat_cap; + + if (bat_cap->bat_last_full_cap != BSDO_BAT_CAP_UNKNOWN) + wh = bat_cap->bat_last_full_cap; + else if (bat_cap->bat_design_cap != BSDO_BAT_CAP_UNKNOWN) + wh = bat_cap->bat_design_cap; + else + wh = BSDO_BAT_CAP_UNKNOWN; + + if (wh != BSDO_BAT_CAP_UNKNOWN) + wh = wh * soc / 1000; + + return wh; +} + +static int tcpm_update_bsdo( + struct pd_port *pd_port, enum pd_battery_reference ref, + uint16_t soc, uint16_t wh, uint8_t status) +{ + uint8_t status_old; + struct pd_battery_info *battery_info; + + battery_info = pd_get_battery_info(pd_port, ref); + if (battery_info == NULL) + return TCPM_ERROR_PARAMETER; + + status_old = BSDO_BAT_INFO(battery_info->bat_status); + + if (soc != 0) + wh = tcpm_calc_battery_cap(battery_info, soc); + + battery_info->bat_status = BSDO(wh, status); + + if (status_old != status) { + tcpm_alert_bat_changed(pd_port, ref); + return TCPM_ALERT; + } + + return TCPM_SUCCESS; +} + +int tcpm_update_bat_status_wh(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t wh) +{ + int ret; + + mutex_lock(&tcpc->pd_port.pd_lock); + ret = tcpm_update_bat_status_wh_no_mutex(tcpc, ref, status, wh); + mutex_unlock(&tcpc->pd_port.pd_lock); + + if (ret == TCPM_ALERT) { + ret = TCPM_SUCCESS; + tcpm_put_tcp_dummy_event(tcpc); + } + + return ret; +} + +int tcpm_update_bat_status_wh_no_mutex(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t wh) +{ + return tcpm_update_bsdo(&tcpc->pd_port, ref, 0, wh, status); +} + +int tcpm_update_bat_status_soc(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t soc) +{ + int ret; + + mutex_lock(&tcpc->pd_port.pd_lock); + ret = tcpm_update_bat_status_soc_no_mutex(tcpc, ref, status, soc); + mutex_unlock(&tcpc->pd_port.pd_lock); + + if (ret == TCPM_ALERT) { + ret = TCPM_SUCCESS; + tcpm_put_tcp_dummy_event(tcpc); + } + + return ret; +} + +int tcpm_update_bat_status_soc_no_mutex(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint8_t status, uint16_t soc) +{ + return tcpm_update_bsdo(&tcpc->pd_port, ref, soc, 0, status); +} + +int tcpm_update_bat_last_full(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint16_t wh) +{ + int ret; + + mutex_lock(&tcpc->pd_port.pd_lock); + ret = tcpm_update_bat_last_full_no_mutex(tcpc, ref, wh); + mutex_unlock(&tcpc->pd_port.pd_lock); + + return ret; +} + +int tcpm_update_bat_last_full_no_mutex(struct tcpc_device *tcpc, + enum pd_battery_reference ref, uint16_t wh) +{ + struct pd_battery_info *battery_info; + + battery_info = pd_get_battery_info(&tcpc->pd_port, ref); + if (battery_info == NULL) + return TCPM_ERROR_PARAMETER; + + battery_info->bat_cap.bat_last_full_cap = wh; + return TCPM_SUCCESS; +} + +#endif /* CONFIG_USB_PD_REV30_BAT_INFO */ + +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL + +static void tcpm_alert_status_changed( + struct tcpc_device *tcpc, uint8_t ado_type) +{ +#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL + tcpc->pd_port.pe_data.local_alert |= ADO(ado_type, 0, 0); +#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */ +} + +int tcpm_update_pd_status_temp(struct tcpc_device *tcpc, + enum pd_present_temperature_flag ptf_new, uint8_t temperature) +{ +#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL_TEMP + uint8_t ado_type = 0; + enum pd_present_temperature_flag ptf_now; + struct pd_port *pd_port = &tcpc->pd_port; + + mutex_lock(&pd_port->pd_lock); + + ptf_now = PD_STATUS_TEMP_PTF(pd_port->pd_status_temp_status); + + if (ptf_now != ptf_new) { + if ((ptf_new >= PD_PTF_WARNING) || + (ptf_now >= PD_PTF_WARNING)) + ado_type |= ADO_ALERT_OPER_CHANGED; + + if (ptf_new == PD_PTF_OVER_TEMP) { + ado_type |= ADO_ALERT_OTP; + pd_port->pe_data.pd_status_event |= PD_STATUS_EVENT_OTP; + } + + pd_port->pd_status_temp_status = + PD_STATUS_TEMP_SET_PTF(ptf_new); + } + + pd_port->pd_status_temp = temperature; + tcpm_alert_status_changed(tcpc, ado_type); + mutex_unlock(&pd_port->pd_lock); + + if (ado_type) + tcpm_put_tcp_dummy_event(tcpc); + + return TCPM_SUCCESS; +#else + return TCPM_ERROR_NO_IMPLEMENT; +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL_TEMP */ +} + +int tcpm_update_pd_status_input( + struct tcpc_device *tcpc, uint8_t input_new, uint8_t mask) +{ + uint8_t input; + uint8_t ado_type = 0; + struct pd_port *pd_port = &tcpc->pd_port; + + mutex_lock(&pd_port->pd_lock); + input = pd_port->pd_status_present_in; + input &= ~mask; + input |= (input_new & mask); + + if (input != pd_port->pd_status_present_in) { + ado_type = ADO_ALERT_SRC_IN_CHANGED; + pd_port->pd_status_present_in = input; + } + + tcpm_alert_status_changed(tcpc, ado_type); + mutex_unlock(&pd_port->pd_lock); + + if (ado_type) + tcpm_put_tcp_dummy_event(tcpc); + + return TCPM_SUCCESS; +} + +int tcpm_update_pd_status_bat_input( + struct tcpc_device *tcpc, uint8_t bat_input, uint8_t bat_mask) +{ + uint8_t input; + uint8_t ado_type = 0; + const uint8_t flag = PD_STATUS_INPUT_INT_POWER_BAT; + struct pd_port *pd_port = &tcpc->pd_port; + + mutex_lock(&pd_port->pd_lock); + input = pd_port->pd_status_bat_in; + input &= ~bat_mask; + input |= (bat_input & bat_mask); + + if (input != pd_port->pd_status_bat_in) { + if (input) + pd_port->pd_status_present_in |= flag; + else + pd_port->pd_status_present_in &= ~flag; + + ado_type = ADO_ALERT_SRC_IN_CHANGED; + } + + tcpm_alert_status_changed(tcpc, ado_type); + mutex_unlock(&pd_port->pd_lock); + + if (ado_type) + tcpm_put_tcp_dummy_event(tcpc); + + return TCPM_SUCCESS; +} + +int tcpm_update_pd_status_event( + struct tcpc_device *tcpc, uint8_t evt) +{ + uint8_t ado_type = 0; + struct pd_port *pd_port = &tcpc->pd_port; + + if (evt & PD_STASUS_EVENT_OCP) + ado_type |= ADO_ALERT_OCP; + + if (evt & PD_STATUS_EVENT_OTP) + ado_type |= ADO_ALERT_OTP; + + if (evt & PD_STATUS_EVENT_OVP) + ado_type |= ADO_ALERT_OVP; + + evt &= PD_STASUS_EVENT_MASK; + + mutex_lock(&pd_port->pd_lock); + pd_port->pe_data.pd_status_event |= evt; + tcpm_alert_status_changed(tcpc, ado_type); + mutex_unlock(&pd_port->pd_lock); + + if (ado_type) + tcpm_put_tcp_dummy_event(tcpc); + + return TCPM_SUCCESS; +} + +#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */ + +#ifdef CONFIG_USB_PD_BLOCK_TCPM + +#if TCPM_DBG_ENABLE +static const char * const bk_event_ret_name[] = { + "OK", + "Unknown", /* or not support by TCPM */ + "NotReady", + "LocalCap", + "PartnerCap", + "SameRole", + "InvalidReq", + "RepeatReq", + "WrongDR", + "PDRev", + + "Detach", + "SReset0", + "SReset1", + "HReset0", + "HReset1", + "Recovery", + "BIST", + "PEBusy", + + "Wait", + "Reject", + "TOUT", + "NAK", + "NotSupport", + + "BKTOUT", + "NoResponse", +}; +#endif /* TCPM_DBG_ENABLE */ + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND +static inline void tcpm_dpm_bk_copy_data(struct pd_port *pd_port) +{ + uint8_t size = pd_port->tcpm_bk_cb_data_max; + + if (size >= pd_get_msg_data_size(pd_port)) + size = pd_get_msg_data_size(pd_port); + + if (pd_port->tcpm_bk_cb_data != NULL) { + memcpy(pd_port->tcpm_bk_cb_data, + pd_get_msg_data_payload(pd_port), size); + } +} +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + +int tcpm_dpm_bk_event_cb( + struct tcpc_device *tcpc, int ret, struct tcp_dpm_event *event) +{ + struct pd_port *pd_port = &tcpc->pd_port; + + if (pd_port->tcpm_bk_event_id != event->event_id) { + TCPM_DBG("bk_event_cb_dummy: expect:%d real:%d\n", + pd_port->tcpm_bk_event_id, event->event_id); + return 0; + } + + pd_port->tcpm_bk_ret = ret; + pd_port->tcpm_bk_done = true; + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + if (ret == TCP_DPM_RET_SUCCESS) + tcpm_dpm_bk_copy_data(pd_port); +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + + wake_up(&pd_port->tcpm_bk_wait_que); + return 0; +} + +static inline int __tcpm_dpm_wait_bk_event( + struct pd_port *pd_port, uint32_t tout_ms) +{ + int ret = TCP_DPM_RET_BK_TIMEOUT; + + wait_event_timeout(pd_port->tcpm_bk_wait_que, pd_port->tcpm_bk_done, + msecs_to_jiffies(tout_ms)); + + if (pd_port->tcpm_bk_done) + return pd_port->tcpm_bk_ret; + mutex_lock(&pd_port->pd_lock); + + pd_port->tcpm_bk_event_id = TCP_DPM_EVT_UNKONW; + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + pd_port->tcpm_bk_cb_data = NULL; +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + + mutex_unlock(&pd_port->pd_lock); + + return ret; +} + +int tcpm_dpm_wait_bk_event(struct pd_port *pd_port, uint32_t tout_ms) +{ + int ret = __tcpm_dpm_wait_bk_event(pd_port, tout_ms); +#if TCPM_DBG_ENABLE + struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc; + + if (ret < TCP_DPM_RET_NR && ret >= 0 + && ret < ARRAY_SIZE(bk_event_ret_name)) + TCPM_DBG("bk_event_cb -> %s\n", bk_event_ret_name[ret]); +#endif /* TCPM_DBG_ENABLE */ + + return ret; +} + +#ifdef CONFIG_MTK_HANDLE_PPS_TIMEOUT +static void mtk_handle_tcp_event_result( + struct tcpc_device *tcpc, struct tcp_dpm_event *event, int ret) +{ + struct tcp_dpm_event evt_hreset = { + .event_id = TCP_DPM_EVT_HARD_RESET, + }; + + if (ret == TCPM_SUCCESS || ret == TCP_DPM_RET_NOT_SUPPORT) + return; + + if (event->event_id == TCP_DPM_EVT_GET_STATUS + || event->event_id == TCP_DPM_EVT_GET_PPS_STATUS) { + tcpm_put_tcp_dpm_event(tcpc, &evt_hreset); + } +} +#endif /* CONFIG_MTK_HANDLE_PPS_TIMEOUT */ + +static int __tcpm_put_tcp_dpm_event_bk( + struct tcpc_device *tcpc, struct tcp_dpm_event *event, + uint32_t tout_ms, uint8_t *data, uint8_t size) +{ + int ret; + struct pd_port *pd_port = &tcpc->pd_port; + + pd_port->tcpm_bk_done = false; + pd_port->tcpm_bk_event_id = event->event_id; + +#ifdef CONFIG_USB_PD_TCPM_CB_2ND + pd_port->tcpm_bk_cb_data = data; + pd_port->tcpm_bk_cb_data_max = size; +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + + ret = tcpm_put_tcp_dpm_event(tcpc, event); + if (ret != TCPM_SUCCESS) + return ret; + + return tcpm_dpm_wait_bk_event(pd_port, tout_ms); +} + +static int tcpm_put_tcp_dpm_event_bk( + struct tcpc_device *tcpc, struct tcp_dpm_event *event, + uint32_t tout_ms, uint8_t *data, uint8_t size) +{ + int ret; + uint8_t retry = CONFIG_USB_PD_TCPM_CB_RETRY; + struct pd_port *pd_port = &tcpc->pd_port; + + event->event_cb = tcpm_dpm_bk_event_cb; + + mutex_lock(&pd_port->tcpm_bk_lock); + + while (1) { + ret = __tcpm_put_tcp_dpm_event_bk( + tcpc, event, tout_ms, data, size); + + if ((ret != TCP_DPM_RET_TIMEOUT) || (retry == 0)) + break; + + retry--; + } + + mutex_unlock(&pd_port->tcpm_bk_lock); + +#ifndef CONFIG_USB_PD_TCPM_CB_2ND + if ((data != NULL) && (ret == TCPM_SUCCESS)) + return TCPM_ERROR_EXPECT_CB2; +#endif /* CONFIG_USB_PD_TCPM_CB_2ND */ + + if (ret == TCP_DPM_RET_DENIED_REPEAT_REQUEST) + ret = TCPM_SUCCESS; + +#ifdef CONFIG_MTK_HANDLE_PPS_TIMEOUT + mtk_handle_tcp_event_result(tcpc, event, ret); +#endif /* CONFIG_MTK_HANDLE_PPS_TIMEOUT */ + + return ret; +} + +#endif /* CONFIG_USB_PD_BLOCK_TCPM */ + +#endif /* CONFIG_USB_POWER_DELIVERY */