summaryrefslogtreecommitdiff
path: root/drivers/usb/pd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/pd')
-rw-r--r--drivers/usb/pd/Kconfig25
-rw-r--r--drivers/usb/pd/Makefile6
-rw-r--r--drivers/usb/pd/policy_engine.c3436
-rw-r--r--drivers/usb/pd/qpnp-pdphy.c917
-rw-r--r--drivers/usb/pd/usbpd.h106
5 files changed, 4490 insertions, 0 deletions
diff --git a/drivers/usb/pd/Kconfig b/drivers/usb/pd/Kconfig
new file mode 100644
index 000000000000..cc88df495f6e
--- /dev/null
+++ b/drivers/usb/pd/Kconfig
@@ -0,0 +1,25 @@
+#
+# USB Power Delivery driver configuration
+#
+menu "USB Power Delivery"
+
+config USB_PD
+ def_bool n
+
+config USB_PD_POLICY
+ tristate "USB Power Delivery Protocol and Policy Engine"
+ depends on EXTCON
+ depends on DUAL_ROLE_USB_INTF
+ select USB_PD
+ help
+ Say Y here to enable USB PD protocol and policy engine.
+
+config QPNP_USB_PDPHY
+ tristate "QPNP USB Power Delivery PHY"
+ depends on SPMI
+ help
+ Say Y here to enable QPNP USB PD PHY peripheral driver
+ which communicates over the SPMI bus. The is used to handle
+ the PHY layer communication of the Power Delivery stack.
+
+endmenu
diff --git a/drivers/usb/pd/Makefile b/drivers/usb/pd/Makefile
new file mode 100644
index 000000000000..f48707026799
--- /dev/null
+++ b/drivers/usb/pd/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for USB Power Delivery drivers
+#
+
+obj-$(CONFIG_USB_PD_POLICY) += policy_engine.o
+obj-$(CONFIG_QPNP_USB_PDPHY) += qpnp-pdphy.o
diff --git a/drivers/usb/pd/policy_engine.c b/drivers/usb/pd/policy_engine.c
new file mode 100644
index 000000000000..f9f47da8a88b
--- /dev/null
+++ b/drivers/usb/pd/policy_engine.c
@@ -0,0 +1,3436 @@
+/* Copyright (c) 2016-2017, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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 <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+#include <linux/ipc_logging.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/extcon.h>
+#include <linux/usb/class-dual-role.h>
+#include <linux/usb/usbpd.h>
+#include "usbpd.h"
+
+/* To start USB stack for USB3.1 complaince testing */
+static bool usb_compliance_mode;
+module_param(usb_compliance_mode, bool, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(usb_compliance_mode, "Start USB stack for USB3.1 compliance testing");
+
+static bool disable_usb_pd;
+module_param(disable_usb_pd, bool, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(disable_usb_pd, "Disable USB PD for USB3.1 compliance testing");
+
+enum usbpd_state {
+ PE_UNKNOWN,
+ PE_ERROR_RECOVERY,
+ PE_SRC_DISABLED,
+ PE_SRC_STARTUP,
+ PE_SRC_SEND_CAPABILITIES,
+ PE_SRC_SEND_CAPABILITIES_WAIT, /* substate to wait for Request */
+ PE_SRC_NEGOTIATE_CAPABILITY,
+ PE_SRC_TRANSITION_SUPPLY,
+ PE_SRC_READY,
+ PE_SRC_HARD_RESET,
+ PE_SRC_SOFT_RESET,
+ PE_SRC_SEND_SOFT_RESET,
+ PE_SRC_DISCOVERY,
+ PE_SRC_TRANSITION_TO_DEFAULT,
+ 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_SOFT_RESET,
+ PE_SNK_SEND_SOFT_RESET,
+ PE_SNK_TRANSITION_TO_DEFAULT,
+ PE_DRS_SEND_DR_SWAP,
+ PE_PRS_SNK_SRC_SEND_SWAP,
+ PE_PRS_SNK_SRC_TRANSITION_TO_OFF,
+ PE_PRS_SNK_SRC_SOURCE_ON,
+ PE_PRS_SRC_SNK_SEND_SWAP,
+ PE_PRS_SRC_SNK_TRANSITION_TO_OFF,
+ PE_PRS_SRC_SNK_WAIT_SOURCE_ON,
+ PE_VCS_WAIT_FOR_VCONN,
+};
+
+static const char * const usbpd_state_strings[] = {
+ "UNKNOWN",
+ "ERROR_RECOVERY",
+ "SRC_Disabled",
+ "SRC_Startup",
+ "SRC_Send_Capabilities",
+ "SRC_Send_Capabilities (Wait for Request)",
+ "SRC_Negotiate_Capability",
+ "SRC_Transition_Supply",
+ "SRC_Ready",
+ "SRC_Hard_Reset",
+ "SRC_Soft_Reset",
+ "SRC_Send_Soft_Reset",
+ "SRC_Discovery",
+ "SRC_Transition_to_default",
+ "SNK_Startup",
+ "SNK_Discovery",
+ "SNK_Wait_for_Capabilities",
+ "SNK_Evaluate_Capability",
+ "SNK_Select_Capability",
+ "SNK_Transition_Sink",
+ "SNK_Ready",
+ "SNK_Hard_Reset",
+ "SNK_Soft_Reset",
+ "SNK_Send_Soft_Reset",
+ "SNK_Transition_to_default",
+ "DRS_Send_DR_Swap",
+ "PRS_SNK_SRC_Send_Swap",
+ "PRS_SNK_SRC_Transition_to_off",
+ "PRS_SNK_SRC_Source_on",
+ "PRS_SRC_SNK_Send_Swap",
+ "PRS_SRC_SNK_Transition_to_off",
+ "PRS_SRC_SNK_Wait_Source_on",
+ "VCS_Wait_for_VCONN",
+};
+
+enum usbpd_control_msg_type {
+ MSG_RESERVED = 0,
+ MSG_GOODCRC,
+ MSG_GOTOMIN,
+ MSG_ACCEPT,
+ MSG_REJECT,
+ MSG_PING,
+ MSG_PS_RDY,
+ MSG_GET_SOURCE_CAP,
+ MSG_GET_SINK_CAP,
+ MSG_DR_SWAP,
+ MSG_PR_SWAP,
+ MSG_VCONN_SWAP,
+ MSG_WAIT,
+ MSG_SOFT_RESET,
+};
+
+enum usbpd_data_msg_type {
+ MSG_SOURCE_CAPABILITIES = 1,
+ MSG_REQUEST,
+ MSG_BIST,
+ MSG_SINK_CAPABILITIES,
+ MSG_VDM = 0xF,
+};
+
+enum vdm_state {
+ VDM_NONE,
+ DISCOVERED_ID,
+ DISCOVERED_SVIDS,
+ DISCOVERED_MODES,
+ MODE_ENTERED,
+ MODE_EXITED,
+};
+
+static void *usbpd_ipc_log;
+#define usbpd_dbg(dev, fmt, ...) do { \
+ ipc_log_string(usbpd_ipc_log, "%s: %s: " fmt, dev_name(dev), __func__, \
+ ##__VA_ARGS__); \
+ dev_dbg(dev, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define usbpd_info(dev, fmt, ...) do { \
+ ipc_log_string(usbpd_ipc_log, "%s: %s: " fmt, dev_name(dev), __func__, \
+ ##__VA_ARGS__); \
+ dev_info(dev, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define usbpd_warn(dev, fmt, ...) do { \
+ ipc_log_string(usbpd_ipc_log, "%s: %s: " fmt, dev_name(dev), __func__, \
+ ##__VA_ARGS__); \
+ dev_warn(dev, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define usbpd_err(dev, fmt, ...) do { \
+ ipc_log_string(usbpd_ipc_log, "%s: %s: " fmt, dev_name(dev), __func__, \
+ ##__VA_ARGS__); \
+ dev_err(dev, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define NUM_LOG_PAGES 10
+
+/* Timeouts (in ms) */
+#define ERROR_RECOVERY_TIME 25
+#define SENDER_RESPONSE_TIME 26
+#define SINK_WAIT_CAP_TIME 500
+#define PS_TRANSITION_TIME 450
+#define SRC_CAP_TIME 120
+#define SRC_TRANSITION_TIME 25
+#define SRC_RECOVER_TIME 750
+#define PS_HARD_RESET_TIME 25
+#define PS_SOURCE_ON 400
+#define PS_SOURCE_OFF 750
+#define FIRST_SOURCE_CAP_TIME 200
+#define VDM_BUSY_TIME 50
+#define VCONN_ON_TIME 100
+
+/* tPSHardReset + tSafe0V */
+#define SNK_HARD_RESET_VBUS_OFF_TIME (35 + 650)
+
+/* tSrcRecover + tSrcTurnOn */
+#define SNK_HARD_RESET_VBUS_ON_TIME (1000 + 275)
+
+#define PD_CAPS_COUNT 50
+
+#define PD_MAX_MSG_ID 7
+
+#define PD_MAX_DATA_OBJ 7
+
+#define PD_MSG_HDR(type, dr, pr, id, cnt, rev) \
+ (((type) & 0xF) | ((dr) << 5) | (rev << 6) | \
+ ((pr) << 8) | ((id) << 9) | ((cnt) << 12))
+#define PD_MSG_HDR_COUNT(hdr) (((hdr) >> 12) & 7)
+#define PD_MSG_HDR_TYPE(hdr) ((hdr) & 0xF)
+#define PD_MSG_HDR_ID(hdr) (((hdr) >> 9) & 7)
+#define PD_MSG_HDR_REV(hdr) (((hdr) >> 6) & 3)
+
+#define PD_RDO_FIXED(obj, gb, mismatch, usb_comm, no_usb_susp, curr1, curr2) \
+ (((obj) << 28) | ((gb) << 27) | ((mismatch) << 26) | \
+ ((usb_comm) << 25) | ((no_usb_susp) << 24) | \
+ ((curr1) << 10) | (curr2))
+
+#define PD_RDO_AUGMENTED(obj, mismatch, usb_comm, no_usb_susp, volt, curr) \
+ (((obj) << 28) | ((mismatch) << 26) | ((usb_comm) << 25) | \
+ ((no_usb_susp) << 24) | ((volt) << 9) | (curr))
+
+#define PD_RDO_OBJ_POS(rdo) ((rdo) >> 28 & 7)
+#define PD_RDO_GIVEBACK(rdo) ((rdo) >> 27 & 1)
+#define PD_RDO_MISMATCH(rdo) ((rdo) >> 26 & 1)
+#define PD_RDO_USB_COMM(rdo) ((rdo) >> 25 & 1)
+#define PD_RDO_NO_USB_SUSP(rdo) ((rdo) >> 24 & 1)
+#define PD_RDO_FIXED_CURR(rdo) ((rdo) >> 10 & 0x3FF)
+#define PD_RDO_FIXED_CURR_MINMAX(rdo) ((rdo) & 0x3FF)
+#define PD_RDO_PROG_VOLTAGE(rdo) ((rdo) >> 9 & 0x7FF)
+#define PD_RDO_PROG_CURR(rdo) ((rdo) & 0x7F)
+
+#define PD_SRC_PDO_TYPE(pdo) (((pdo) >> 30) & 3)
+#define PD_SRC_PDO_TYPE_FIXED 0
+#define PD_SRC_PDO_TYPE_BATTERY 1
+#define PD_SRC_PDO_TYPE_VARIABLE 2
+#define PD_SRC_PDO_TYPE_AUGMENTED 3
+
+#define PD_SRC_PDO_FIXED_PR_SWAP(pdo) (((pdo) >> 29) & 1)
+#define PD_SRC_PDO_FIXED_USB_SUSP(pdo) (((pdo) >> 28) & 1)
+#define PD_SRC_PDO_FIXED_EXT_POWERED(pdo) (((pdo) >> 27) & 1)
+#define PD_SRC_PDO_FIXED_USB_COMM(pdo) (((pdo) >> 26) & 1)
+#define PD_SRC_PDO_FIXED_DR_SWAP(pdo) (((pdo) >> 25) & 1)
+#define PD_SRC_PDO_FIXED_PEAK_CURR(pdo) (((pdo) >> 20) & 3)
+#define PD_SRC_PDO_FIXED_VOLTAGE(pdo) (((pdo) >> 10) & 0x3FF)
+#define PD_SRC_PDO_FIXED_MAX_CURR(pdo) ((pdo) & 0x3FF)
+
+#define PD_SRC_PDO_VAR_BATT_MAX_VOLT(pdo) (((pdo) >> 20) & 0x3FF)
+#define PD_SRC_PDO_VAR_BATT_MIN_VOLT(pdo) (((pdo) >> 10) & 0x3FF)
+#define PD_SRC_PDO_VAR_BATT_MAX(pdo) ((pdo) & 0x3FF)
+
+#define PD_APDO_PPS(pdo) (((pdo) >> 28) & 3)
+#define PD_APDO_MAX_VOLT(pdo) (((pdo) >> 17) & 0xFF)
+#define PD_APDO_MIN_VOLT(pdo) (((pdo) >> 8) & 0xFF)
+#define PD_APDO_MAX_CURR(pdo) ((pdo) & 0x7F)
+
+/* Vendor Defined Messages */
+#define MAX_CRC_RECEIVE_TIME 9 /* ~(2 * tReceive_max(1.1ms) * # retry 4) */
+#define MAX_VDM_RESPONSE_TIME 60 /* 2 * tVDMSenderResponse_max(30ms) */
+#define MAX_VDM_BUSY_TIME 100 /* 2 * tVDMBusy (50ms) */
+
+#define PD_SNK_PDO_FIXED(prs, hc, uc, usb_comm, drs, volt, curr) \
+ (((prs) << 29) | ((hc) << 28) | ((uc) << 27) | ((usb_comm) << 26) | \
+ ((drs) << 25) | ((volt) << 10) | (curr))
+
+/* VDM header is the first 32-bit object following the 16-bit PD header */
+#define VDM_HDR_SVID(hdr) ((hdr) >> 16)
+#define VDM_IS_SVDM(hdr) ((hdr) & 0x8000)
+#define SVDM_HDR_OBJ_POS(hdr) (((hdr) >> 8) & 0x7)
+#define SVDM_HDR_CMD_TYPE(hdr) (((hdr) >> 6) & 0x3)
+#define SVDM_HDR_CMD(hdr) ((hdr) & 0x1f)
+
+#define SVDM_HDR(svid, ver, obj, cmd_type, cmd) \
+ (((svid) << 16) | (1 << 15) | ((ver) << 13) \
+ | ((obj) << 8) | ((cmd_type) << 6) | (cmd))
+
+/* discover id response vdo bit fields */
+#define ID_HDR_USB_HOST BIT(31)
+#define ID_HDR_USB_DEVICE BIT(30)
+#define ID_HDR_MODAL_OPR BIT(26)
+#define ID_HDR_PRODUCT_TYPE(n) ((n) >> 27)
+#define ID_HDR_PRODUCT_PER_MASK (2 << 27)
+#define ID_HDR_PRODUCT_HUB 1
+#define ID_HDR_PRODUCT_PER 2
+#define ID_HDR_PRODUCT_AMA 5
+#define ID_HDR_VID 0x05c6 /* qcom */
+#define PROD_VDO_PID 0x0a00 /* TBD */
+
+static bool check_vsafe0v = true;
+module_param(check_vsafe0v, bool, S_IRUSR | S_IWUSR);
+
+static int min_sink_current = 900;
+module_param(min_sink_current, int, S_IRUSR | S_IWUSR);
+
+static const u32 default_src_caps[] = { 0x36019096 }; /* VSafe5V @ 1.5A */
+static const u32 default_snk_caps[] = { 0x2601912C }; /* VSafe5V @ 3A */
+
+struct vdm_tx {
+ u32 data[7];
+ int size;
+};
+
+struct rx_msg {
+ u8 type;
+ u8 len;
+ u32 payload[7];
+ struct list_head entry;
+};
+
+#define IS_DATA(m, t) ((m) && ((m)->len) && ((m)->type == (t)))
+#define IS_CTRL(m, t) ((m) && !((m)->len) && ((m)->type == (t)))
+
+struct usbpd {
+ struct device dev;
+ struct workqueue_struct *wq;
+ struct work_struct sm_work;
+ struct hrtimer timer;
+ bool sm_queued;
+
+ struct extcon_dev *extcon;
+
+ enum usbpd_state current_state;
+ bool hard_reset_recvd;
+ struct list_head rx_q;
+ spinlock_t rx_lock;
+
+ u32 received_pdos[PD_MAX_DATA_OBJ];
+ u16 src_cap_id;
+ u8 selected_pdo;
+ u8 requested_pdo;
+ u32 rdo; /* can be either source or sink */
+ int current_voltage; /* uV */
+ int requested_voltage; /* uV */
+ int requested_current; /* mA */
+ bool pd_connected;
+ bool in_explicit_contract;
+ bool peer_usb_comm;
+ bool peer_pr_swap;
+ bool peer_dr_swap;
+
+ u32 sink_caps[7];
+ int num_sink_caps;
+
+ struct power_supply *usb_psy;
+ struct notifier_block psy_nb;
+
+ enum power_supply_typec_mode typec_mode;
+ enum power_supply_type psy_type;
+ enum power_supply_typec_power_role forced_pr;
+ bool vbus_present;
+
+ enum pd_spec_rev spec_rev;
+ enum data_role current_dr;
+ enum power_role current_pr;
+ bool in_pr_swap;
+ bool pd_phy_opened;
+ bool send_request;
+ struct completion is_ready;
+
+ struct mutex swap_lock;
+ struct dual_role_phy_instance *dual_role;
+ struct dual_role_phy_desc dr_desc;
+ bool send_pr_swap;
+ bool send_dr_swap;
+
+ struct regulator *vbus;
+ struct regulator *vconn;
+ bool vbus_enabled;
+ bool vconn_enabled;
+ bool vconn_is_external;
+
+ u8 tx_msgid;
+ u8 rx_msgid;
+ int caps_count;
+ int hard_reset_count;
+
+ enum vdm_state vdm_state;
+ u16 *discovered_svids;
+ int num_svids;
+ struct vdm_tx *vdm_tx;
+ struct vdm_tx *vdm_tx_retry;
+ struct list_head svid_handlers;
+
+ struct list_head instance;
+};
+
+static LIST_HEAD(_usbpd); /* useful for debugging */
+
+static const unsigned int usbpd_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_USB_CC,
+ EXTCON_USB_SPEED,
+ EXTCON_NONE,
+};
+
+/* EXTCON_USB and EXTCON_USB_HOST are mutually exclusive */
+static const u32 usbpd_extcon_exclusive[] = {0x3, 0};
+
+enum plug_orientation usbpd_get_plug_orientation(struct usbpd *pd)
+{
+ int ret;
+ union power_supply_propval val;
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION, &val);
+ if (ret)
+ return ORIENTATION_NONE;
+
+ return val.intval;
+}
+EXPORT_SYMBOL(usbpd_get_plug_orientation);
+
+static inline void stop_usb_host(struct usbpd *pd)
+{
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_HOST, 0);
+}
+
+static inline void start_usb_host(struct usbpd *pd, bool ss)
+{
+ enum plug_orientation cc = usbpd_get_plug_orientation(pd);
+
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_CC,
+ cc == ORIENTATION_CC2);
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_SPEED, ss);
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_HOST, 1);
+}
+
+static inline void stop_usb_peripheral(struct usbpd *pd)
+{
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB, 0);
+}
+
+static inline void start_usb_peripheral(struct usbpd *pd)
+{
+ enum plug_orientation cc = usbpd_get_plug_orientation(pd);
+
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_CC,
+ cc == ORIENTATION_CC2);
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_SPEED, 1);
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB, 1);
+}
+
+static int set_power_role(struct usbpd *pd, enum power_role pr)
+{
+ union power_supply_propval val = {0};
+
+ switch (pr) {
+ case PR_NONE:
+ val.intval = POWER_SUPPLY_TYPEC_PR_NONE;
+ break;
+ case PR_SINK:
+ val.intval = POWER_SUPPLY_TYPEC_PR_SINK;
+ break;
+ case PR_SRC:
+ val.intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
+ break;
+ }
+
+ return power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &val);
+}
+
+static struct usbpd_svid_handler *find_svid_handler(struct usbpd *pd, u16 svid)
+{
+ struct usbpd_svid_handler *handler;
+
+ list_for_each_entry(handler, &pd->svid_handlers, entry)
+ if (svid == handler->svid)
+ return handler;
+
+ return NULL;
+}
+
+/* Reset protocol layer */
+static inline void pd_reset_protocol(struct usbpd *pd)
+{
+ /*
+ * first Rx ID should be 0; set this to a sentinel of -1 so that in
+ * phy_msg_received() we can check if we had seen it before.
+ */
+ pd->rx_msgid = -1;
+ pd->tx_msgid = 0;
+ pd->send_request = false;
+ pd->send_pr_swap = false;
+ pd->send_dr_swap = false;
+}
+
+static int pd_send_msg(struct usbpd *pd, u8 msg_type, const u32 *data,
+ size_t num_data, enum pd_sop_type sop)
+{
+ int ret;
+ u16 hdr;
+
+ hdr = PD_MSG_HDR(msg_type, pd->current_dr, pd->current_pr,
+ pd->tx_msgid, num_data, pd->spec_rev);
+
+ ret = pd_phy_write(hdr, (u8 *)data, num_data * sizeof(u32), sop);
+ if (ret)
+ return ret;
+
+ pd->tx_msgid = (pd->tx_msgid + 1) & PD_MAX_MSG_ID;
+ return 0;
+}
+
+static int pd_select_pdo(struct usbpd *pd, int pdo_pos, int uv, int ua)
+{
+ int curr;
+ int max_current;
+ bool mismatch = false;
+ u8 type;
+ u32 pdo = pd->received_pdos[pdo_pos - 1];
+
+ type = PD_SRC_PDO_TYPE(pdo);
+ if (type == PD_SRC_PDO_TYPE_FIXED) {
+ curr = max_current = PD_SRC_PDO_FIXED_MAX_CURR(pdo) * 10;
+
+ /*
+ * Check if the PDO has enough current, otherwise set the
+ * Capability Mismatch flag
+ */
+ if (curr < min_sink_current) {
+ mismatch = true;
+ max_current = min_sink_current;
+ }
+
+ pd->requested_voltage =
+ PD_SRC_PDO_FIXED_VOLTAGE(pdo) * 50 * 1000;
+ pd->rdo = PD_RDO_FIXED(pdo_pos, 0, mismatch, 1, 1, curr / 10,
+ max_current / 10);
+ } else if (type == PD_SRC_PDO_TYPE_AUGMENTED) {
+ if ((uv / 100000) > PD_APDO_MAX_VOLT(pdo) ||
+ (uv / 100000) < PD_APDO_MIN_VOLT(pdo) ||
+ (ua / 50000) > PD_APDO_MAX_CURR(pdo) || (ua < 0)) {
+ usbpd_err(&pd->dev, "uv (%d) and ua (%d) out of range of APDO\n",
+ uv, ua);
+ return -EINVAL;
+ }
+
+ curr = ua / 1000;
+ pd->requested_voltage = uv;
+ pd->rdo = PD_RDO_AUGMENTED(pdo_pos, mismatch, 1, 1,
+ uv / 20000, ua / 50000);
+ } else {
+ usbpd_err(&pd->dev, "Only Fixed or Programmable PDOs supported\n");
+ return -ENOTSUPP;
+ }
+
+ /* Can't sink more than 5V if VCONN is sourced from the VBUS input */
+ if (pd->vconn_enabled && !pd->vconn_is_external &&
+ pd->requested_voltage > 5000000)
+ return -ENOTSUPP;
+
+ pd->requested_current = curr;
+ pd->requested_pdo = pdo_pos;
+
+ return 0;
+}
+
+static int pd_eval_src_caps(struct usbpd *pd)
+{
+ int obj_cnt;
+ union power_supply_propval val;
+ u32 first_pdo = pd->received_pdos[0];
+
+ if (PD_SRC_PDO_TYPE(first_pdo) != PD_SRC_PDO_TYPE_FIXED) {
+ usbpd_err(&pd->dev, "First src_cap invalid! %08x\n", first_pdo);
+ return -EINVAL;
+ }
+
+ pd->peer_usb_comm = PD_SRC_PDO_FIXED_USB_COMM(first_pdo);
+ pd->peer_pr_swap = PD_SRC_PDO_FIXED_PR_SWAP(first_pdo);
+ pd->peer_dr_swap = PD_SRC_PDO_FIXED_DR_SWAP(first_pdo);
+
+ val.intval = PD_SRC_PDO_FIXED_USB_SUSP(first_pdo);
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED, &val);
+
+ for (obj_cnt = 1; obj_cnt < PD_MAX_DATA_OBJ; obj_cnt++) {
+ if ((PD_SRC_PDO_TYPE(pd->received_pdos[obj_cnt]) ==
+ PD_SRC_PDO_TYPE_AUGMENTED) &&
+ !PD_APDO_PPS(pd->received_pdos[obj_cnt]))
+ pd->spec_rev = USBPD_REV_30;
+ }
+
+ /* Select the first PDO (vSafe5V) immediately. */
+ pd_select_pdo(pd, 1, 0, 0);
+
+ return 0;
+}
+
+static void pd_send_hard_reset(struct usbpd *pd)
+{
+ union power_supply_propval val = {0};
+
+ usbpd_dbg(&pd->dev, "send hard reset");
+
+ /* Force CC logic to source/sink to keep Rp/Rd unchanged */
+ set_power_role(pd, pd->current_pr);
+ pd->hard_reset_count++;
+ pd_phy_signal(HARD_RESET_SIG);
+ pd->in_pr_swap = false;
+ power_supply_set_property(pd->usb_psy, POWER_SUPPLY_PROP_PR_SWAP, &val);
+}
+
+static void kick_sm(struct usbpd *pd, int ms)
+{
+ pm_stay_awake(&pd->dev);
+ pd->sm_queued = true;
+
+ if (ms)
+ hrtimer_start(&pd->timer, ms_to_ktime(ms), HRTIMER_MODE_REL);
+ else
+ queue_work(pd->wq, &pd->sm_work);
+}
+
+static void phy_sig_received(struct usbpd *pd, enum pd_sig_type sig)
+{
+ union power_supply_propval val = {1};
+
+ if (sig != HARD_RESET_SIG) {
+ usbpd_err(&pd->dev, "invalid signal (%d) received\n", sig);
+ return;
+ }
+
+ usbpd_dbg(&pd->dev, "hard reset received\n");
+
+ /* Force CC logic to source/sink to keep Rp/Rd unchanged */
+ set_power_role(pd, pd->current_pr);
+ pd->hard_reset_recvd = true;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_IN_HARD_RESET, &val);
+
+ kick_sm(pd, 0);
+}
+
+static void phy_msg_received(struct usbpd *pd, enum pd_sop_type sop,
+ u8 *buf, size_t len)
+{
+ struct rx_msg *rx_msg;
+ unsigned long flags;
+ u16 header;
+
+ if (sop != SOP_MSG) {
+ usbpd_err(&pd->dev, "invalid msg type (%d) received; only SOP supported\n",
+ sop);
+ return;
+ }
+
+ if (len < 2) {
+ usbpd_err(&pd->dev, "invalid message received, len=%zd\n", len);
+ return;
+ }
+
+ header = *((u16 *)buf);
+ buf += sizeof(u16);
+ len -= sizeof(u16);
+
+ if (len % 4 != 0) {
+ usbpd_err(&pd->dev, "len=%zd not multiple of 4\n", len);
+ return;
+ }
+
+ /* if MSGID already seen, discard */
+ if (PD_MSG_HDR_ID(header) == pd->rx_msgid &&
+ PD_MSG_HDR_TYPE(header) != MSG_SOFT_RESET) {
+ usbpd_dbg(&pd->dev, "MessageID already seen, discarding\n");
+ return;
+ }
+
+ pd->rx_msgid = PD_MSG_HDR_ID(header);
+
+ /* discard Pings */
+ if (PD_MSG_HDR_TYPE(header) == MSG_PING && !len)
+ return;
+
+ /* check header's count field to see if it matches len */
+ if (PD_MSG_HDR_COUNT(header) != (len / 4)) {
+ usbpd_err(&pd->dev, "header count (%d) mismatch, len=%zd\n",
+ PD_MSG_HDR_COUNT(header), len);
+ return;
+ }
+
+ rx_msg = kzalloc(sizeof(*rx_msg), GFP_KERNEL);
+ if (!rx_msg)
+ return;
+
+ rx_msg->type = PD_MSG_HDR_TYPE(header);
+ rx_msg->len = PD_MSG_HDR_COUNT(header);
+ memcpy(&rx_msg->payload, buf, min(len, sizeof(rx_msg->payload)));
+
+ spin_lock_irqsave(&pd->rx_lock, flags);
+ list_add_tail(&rx_msg->entry, &pd->rx_q);
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+
+ usbpd_dbg(&pd->dev, "received message: type(%d) len(%d)\n",
+ rx_msg->type, rx_msg->len);
+
+ kick_sm(pd, 0);
+}
+
+static void phy_shutdown(struct usbpd *pd)
+{
+ usbpd_dbg(&pd->dev, "shutdown");
+}
+
+static enum hrtimer_restart pd_timeout(struct hrtimer *timer)
+{
+ struct usbpd *pd = container_of(timer, struct usbpd, timer);
+
+ usbpd_dbg(&pd->dev, "timeout");
+ queue_work(pd->wq, &pd->sm_work);
+
+ return HRTIMER_NORESTART;
+}
+
+/* Enters new state and executes actions on entry */
+static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
+{
+ struct pd_phy_params phy_params = {
+ .signal_cb = phy_sig_received,
+ .msg_rx_cb = phy_msg_received,
+ .shutdown_cb = phy_shutdown,
+ .frame_filter_val = FRAME_FILTER_EN_SOP |
+ FRAME_FILTER_EN_HARD_RESET,
+ };
+ union power_supply_propval val = {0};
+ unsigned long flags;
+ int ret;
+
+ usbpd_dbg(&pd->dev, "%s -> %s\n",
+ usbpd_state_strings[pd->current_state],
+ usbpd_state_strings[next_state]);
+
+ pd->current_state = next_state;
+
+ switch (next_state) {
+ case PE_ERROR_RECOVERY: /* perform hard disconnect/reconnect */
+ pd->in_pr_swap = false;
+ pd->current_pr = PR_NONE;
+ set_power_role(pd, PR_NONE);
+ pd->typec_mode = POWER_SUPPLY_TYPEC_NONE;
+ kick_sm(pd, 0);
+ break;
+
+ /* Source states */
+ case PE_SRC_DISABLED:
+ /* are we still connected? */
+ if (pd->typec_mode == POWER_SUPPLY_TYPEC_NONE) {
+ pd->current_pr = PR_NONE;
+ kick_sm(pd, 0);
+ }
+
+ break;
+
+ case PE_SRC_STARTUP:
+ if (pd->current_dr == DR_NONE) {
+ pd->current_dr = DR_DFP;
+ /*
+ * Defer starting USB host mode until PE_SRC_READY or
+ * when PE_SRC_SEND_CAPABILITIES fails
+ */
+ }
+
+ dual_role_instance_changed(pd->dual_role);
+
+ /* Set CC back to DRP toggle for the next disconnect */
+ val.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &val);
+
+ pd_reset_protocol(pd);
+
+ if (!pd->in_pr_swap) {
+ if (pd->pd_phy_opened) {
+ pd_phy_close();
+ pd->pd_phy_opened = false;
+ }
+
+ phy_params.data_role = pd->current_dr;
+ phy_params.power_role = pd->current_pr;
+
+ ret = pd_phy_open(&phy_params);
+ if (ret) {
+ WARN_ON_ONCE(1);
+ usbpd_err(&pd->dev, "error opening PD PHY %d\n",
+ ret);
+ pd->current_state = PE_UNKNOWN;
+ return;
+ }
+
+ pd->pd_phy_opened = true;
+ }
+
+ if (pd->in_pr_swap) {
+ pd->in_pr_swap = false;
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PR_SWAP, &val);
+ }
+
+ /*
+ * A sink might remove its terminations (during some Type-C
+ * compliance tests or a sink attempting to do Try.SRC)
+ * at this point just after we enabled VBUS. Sending PD
+ * messages now would delay detecting the detach beyond the
+ * required timing. Instead, delay sending out the first
+ * source capabilities to allow for the other side to
+ * completely settle CC debounce and allow HW to detect detach
+ * sooner in the meantime. PD spec allows up to
+ * tFirstSourceCap (250ms).
+ */
+ pd->current_state = PE_SRC_SEND_CAPABILITIES;
+ kick_sm(pd, FIRST_SOURCE_CAP_TIME);
+ break;
+
+ case PE_SRC_SEND_CAPABILITIES:
+ kick_sm(pd, 0);
+ break;
+
+ case PE_SRC_NEGOTIATE_CAPABILITY:
+ if (PD_RDO_OBJ_POS(pd->rdo) != 1 ||
+ PD_RDO_FIXED_CURR(pd->rdo) >
+ PD_SRC_PDO_FIXED_MAX_CURR(*default_src_caps) ||
+ PD_RDO_FIXED_CURR_MINMAX(pd->rdo) >
+ PD_SRC_PDO_FIXED_MAX_CURR(*default_src_caps)) {
+ /* send Reject */
+ ret = pd_send_msg(pd, MSG_REJECT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Reject\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ usbpd_err(&pd->dev, "Invalid request: %08x\n", pd->rdo);
+
+ if (pd->in_explicit_contract)
+ usbpd_set_state(pd, PE_SRC_READY);
+ else
+ /*
+ * bypass PE_SRC_Capability_Response and
+ * PE_SRC_Wait_New_Capabilities in this
+ * implementation for simplicity.
+ */
+ usbpd_set_state(pd, PE_SRC_SEND_CAPABILITIES);
+ break;
+ }
+
+ /* PE_SRC_TRANSITION_SUPPLY pseudo-state */
+ ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Accept\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ /* tSrcTransition required after ACCEPT */
+ usleep_range(SRC_TRANSITION_TIME * USEC_PER_MSEC,
+ (SRC_TRANSITION_TIME + 5) * USEC_PER_MSEC);
+
+ /*
+ * Normally a voltage change should occur within tSrcReady
+ * but since we only support VSafe5V there is nothing more to
+ * prepare from the power supply so send PS_RDY right away.
+ */
+ ret = pd_send_msg(pd, MSG_PS_RDY, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending PS_RDY\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ usbpd_set_state(pd, PE_SRC_READY);
+ break;
+
+ case PE_SRC_READY:
+ pd->in_explicit_contract = true;
+ if (pd->current_dr == DR_DFP) {
+ /* don't start USB host until after SVDM discovery */
+ if (pd->vdm_state == VDM_NONE)
+ usbpd_send_svdm(pd, USBPD_SID,
+ USBPD_SVDM_DISCOVER_IDENTITY,
+ SVDM_CMD_TYPE_INITIATOR, 0,
+ NULL, 0);
+ }
+
+ kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
+ complete(&pd->is_ready);
+ dual_role_instance_changed(pd->dual_role);
+ break;
+
+ case PE_SRC_HARD_RESET:
+ case PE_SNK_HARD_RESET:
+ /* are we still connected? */
+ if (pd->typec_mode == POWER_SUPPLY_TYPEC_NONE)
+ pd->current_pr = PR_NONE;
+
+ /* hard reset may sleep; handle it in the workqueue */
+ kick_sm(pd, 0);
+ break;
+
+ case PE_SRC_SEND_SOFT_RESET:
+ case PE_SNK_SEND_SOFT_RESET:
+ pd_reset_protocol(pd);
+
+ ret = pd_send_msg(pd, MSG_SOFT_RESET, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Soft Reset, do Hard Reset\n");
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_HARD_RESET : PE_SNK_HARD_RESET);
+ break;
+ }
+
+ /* wait for ACCEPT */
+ kick_sm(pd, SENDER_RESPONSE_TIME);
+ break;
+
+ /* Sink states */
+ case PE_SNK_STARTUP:
+ if (pd->current_dr == DR_NONE || pd->current_dr == DR_UFP) {
+ pd->current_dr = DR_UFP;
+
+ if (pd->psy_type == POWER_SUPPLY_TYPE_USB ||
+ pd->psy_type == POWER_SUPPLY_TYPE_USB_CDP ||
+ pd->psy_type == POWER_SUPPLY_TYPE_USB_FLOAT ||
+ usb_compliance_mode)
+ start_usb_peripheral(pd);
+ }
+
+ dual_role_instance_changed(pd->dual_role);
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ALLOWED, &val);
+ if (ret) {
+ usbpd_err(&pd->dev, "Unable to read USB PROP_PD_ALLOWED: %d\n",
+ ret);
+ break;
+ }
+
+ if (!val.intval || disable_usb_pd)
+ break;
+
+ pd_reset_protocol(pd);
+
+ if (!pd->in_pr_swap) {
+ if (pd->pd_phy_opened) {
+ pd_phy_close();
+ pd->pd_phy_opened = false;
+ }
+
+ phy_params.data_role = pd->current_dr;
+ phy_params.power_role = pd->current_pr;
+
+ ret = pd_phy_open(&phy_params);
+ if (ret) {
+ WARN_ON_ONCE(1);
+ usbpd_err(&pd->dev, "error opening PD PHY %d\n",
+ ret);
+ pd->current_state = PE_UNKNOWN;
+ return;
+ }
+
+ pd->pd_phy_opened = true;
+ }
+
+ pd->current_voltage = pd->requested_voltage = 5000000;
+ val.intval = pd->requested_voltage; /* set max range to 5V */
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MAX, &val);
+
+ if (!pd->vbus_present) {
+ pd->current_state = PE_SNK_DISCOVERY;
+ /* max time for hard reset to turn vbus back on */
+ kick_sm(pd, SNK_HARD_RESET_VBUS_ON_TIME);
+ break;
+ }
+
+ pd->current_state = PE_SNK_WAIT_FOR_CAPABILITIES;
+ /* fall-through */
+
+ case PE_SNK_WAIT_FOR_CAPABILITIES:
+ spin_lock_irqsave(&pd->rx_lock, flags);
+ if (list_empty(&pd->rx_q))
+ kick_sm(pd, SINK_WAIT_CAP_TIME);
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+ break;
+
+ case PE_SNK_EVALUATE_CAPABILITY:
+ pd->pd_connected = true; /* we know peer is PD capable */
+ pd->hard_reset_count = 0;
+
+ /* evaluate PDOs and select one */
+ ret = pd_eval_src_caps(pd);
+ if (ret < 0) {
+ usbpd_err(&pd->dev, "Invalid src_caps received. Skipping request\n");
+ break;
+ }
+ pd->current_state = PE_SNK_SELECT_CAPABILITY;
+ /* fall-through */
+
+ case PE_SNK_SELECT_CAPABILITY:
+ ret = pd_send_msg(pd, MSG_REQUEST, &pd->rdo, 1, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Request\n");
+ usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
+ break;
+ }
+
+ /* wait for ACCEPT */
+ kick_sm(pd, SENDER_RESPONSE_TIME);
+ break;
+
+ case PE_SNK_TRANSITION_SINK:
+ /* wait for PS_RDY */
+ kick_sm(pd, PS_TRANSITION_TIME);
+ break;
+
+ case PE_SNK_READY:
+ pd->in_explicit_contract = true;
+ kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
+ complete(&pd->is_ready);
+ dual_role_instance_changed(pd->dual_role);
+ break;
+
+ case PE_SNK_TRANSITION_TO_DEFAULT:
+ if (pd->current_dr != DR_UFP) {
+ stop_usb_host(pd);
+ start_usb_peripheral(pd);
+ pd->current_dr = DR_UFP;
+ pd_phy_update_roles(pd->current_dr, pd->current_pr);
+ }
+ if (pd->vconn_enabled) {
+ regulator_disable(pd->vconn);
+ pd->vconn_enabled = false;
+ }
+
+ /* max time for hard reset to turn vbus off */
+ kick_sm(pd, SNK_HARD_RESET_VBUS_OFF_TIME);
+ break;
+
+ case PE_PRS_SNK_SRC_TRANSITION_TO_OFF:
+ val.intval = pd->requested_current = 0; /* suspend charging */
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_CURRENT_MAX, &val);
+
+ pd->in_explicit_contract = false;
+
+ /*
+ * need to update PR bit in message header so that
+ * proper GoodCRC is sent when receiving next PS_RDY
+ */
+ pd_phy_update_roles(pd->current_dr, PR_SRC);
+
+ /* wait for PS_RDY */
+ kick_sm(pd, PS_SOURCE_OFF);
+ break;
+
+ default:
+ usbpd_dbg(&pd->dev, "No action for state %s\n",
+ usbpd_state_strings[pd->current_state]);
+ break;
+ }
+}
+
+int usbpd_register_svid(struct usbpd *pd, struct usbpd_svid_handler *hdlr)
+{
+ if (find_svid_handler(pd, hdlr->svid)) {
+ usbpd_err(&pd->dev, "SVID 0x%04x already registered\n",
+ hdlr->svid);
+ return -EINVAL;
+ }
+
+ /* require connect/disconnect callbacks be implemented */
+ if (!hdlr->connect || !hdlr->disconnect) {
+ usbpd_err(&pd->dev, "SVID 0x%04x connect/disconnect must be non-NULL\n",
+ hdlr->svid);
+ return -EINVAL;
+ }
+
+ usbpd_dbg(&pd->dev, "registered handler for SVID 0x%04x\n", hdlr->svid);
+
+ list_add_tail(&hdlr->entry, &pd->svid_handlers);
+
+ /* already connected with this SVID discovered? */
+ if (pd->vdm_state >= DISCOVERED_SVIDS) {
+ int i;
+
+ for (i = 0; i < pd->num_svids; i++) {
+ if (pd->discovered_svids[i] == hdlr->svid) {
+ hdlr->connect(hdlr);
+ hdlr->discovered = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(usbpd_register_svid);
+
+void usbpd_unregister_svid(struct usbpd *pd, struct usbpd_svid_handler *hdlr)
+{
+ list_del_init(&hdlr->entry);
+}
+EXPORT_SYMBOL(usbpd_unregister_svid);
+
+int usbpd_send_vdm(struct usbpd *pd, u32 vdm_hdr, const u32 *vdos, int num_vdos)
+{
+ struct vdm_tx *vdm_tx;
+
+ if (!pd->in_explicit_contract || pd->vdm_tx)
+ return -EBUSY;
+
+ vdm_tx = kzalloc(sizeof(*vdm_tx), GFP_KERNEL);
+ if (!vdm_tx)
+ return -ENOMEM;
+
+ vdm_tx->data[0] = vdm_hdr;
+ if (vdos && num_vdos)
+ memcpy(&vdm_tx->data[1], vdos, num_vdos * sizeof(u32));
+ vdm_tx->size = num_vdos + 1; /* include the header */
+
+ /* VDM will get sent in PE_SRC/SNK_READY state handling */
+ pd->vdm_tx = vdm_tx;
+
+ /* slight delay before queuing to prioritize handling of incoming VDM */
+ kick_sm(pd, 2);
+
+ return 0;
+}
+EXPORT_SYMBOL(usbpd_send_vdm);
+
+int usbpd_send_svdm(struct usbpd *pd, u16 svid, u8 cmd,
+ enum usbpd_svdm_cmd_type cmd_type, int obj_pos,
+ const u32 *vdos, int num_vdos)
+{
+ u32 svdm_hdr = SVDM_HDR(svid, 0, obj_pos, cmd_type, cmd);
+
+ usbpd_dbg(&pd->dev, "VDM tx: svid:%x cmd:%x cmd_type:%x svdm_hdr:%x\n",
+ svid, cmd, cmd_type, svdm_hdr);
+
+ return usbpd_send_vdm(pd, svdm_hdr, vdos, num_vdos);
+}
+EXPORT_SYMBOL(usbpd_send_svdm);
+
+static void handle_vdm_rx(struct usbpd *pd, struct rx_msg *rx_msg)
+{
+ u32 vdm_hdr = rx_msg->payload[0];
+ u32 *vdos = &rx_msg->payload[1];
+ u16 svid = VDM_HDR_SVID(vdm_hdr);
+ u16 *psvid;
+ u8 i, num_vdos = rx_msg->len - 1; /* num objects minus header */
+ u8 cmd = SVDM_HDR_CMD(vdm_hdr);
+ u8 cmd_type = SVDM_HDR_CMD_TYPE(vdm_hdr);
+ bool has_dp = false;
+ struct usbpd_svid_handler *handler;
+
+ usbpd_dbg(&pd->dev, "VDM rx: svid:%x cmd:%x cmd_type:%x vdm_hdr:%x\n",
+ svid, cmd, cmd_type, vdm_hdr);
+
+ /* if it's a supported SVID, pass the message to the handler */
+ handler = find_svid_handler(pd, svid);
+
+ /* Unstructured VDM */
+ if (!VDM_IS_SVDM(vdm_hdr)) {
+ if (handler && handler->vdm_received)
+ handler->vdm_received(handler, vdm_hdr, vdos, num_vdos);
+ return;
+ }
+
+ /* if this interrupts a previous exchange, abort queued response */
+ if (cmd_type == SVDM_CMD_TYPE_INITIATOR && pd->vdm_tx) {
+ usbpd_dbg(&pd->dev, "Discarding previously queued SVDM tx (SVID:0x%04x)\n",
+ VDM_HDR_SVID(pd->vdm_tx->data[0]));
+
+ kfree(pd->vdm_tx);
+ pd->vdm_tx = NULL;
+ }
+
+ if (handler && handler->svdm_received) {
+ handler->svdm_received(handler, cmd, cmd_type, vdos, num_vdos);
+ return;
+ }
+
+ /* Standard Discovery or unhandled messages go here */
+ switch (cmd_type) {
+ case SVDM_CMD_TYPE_INITIATOR:
+ if (svid == USBPD_SID && cmd == USBPD_SVDM_DISCOVER_IDENTITY) {
+ u32 tx_vdos[3] = {
+ ID_HDR_USB_HOST | ID_HDR_USB_DEVICE |
+ ID_HDR_PRODUCT_PER_MASK | ID_HDR_VID,
+ 0x0, /* TBD: Cert Stat VDO */
+ (PROD_VDO_PID << 16),
+ /* TBD: Get these from gadget */
+ };
+
+ usbpd_send_svdm(pd, USBPD_SID, cmd,
+ SVDM_CMD_TYPE_RESP_ACK, 0, tx_vdos, 3);
+ } else if (cmd != USBPD_SVDM_ATTENTION) {
+ usbpd_send_svdm(pd, svid, cmd, SVDM_CMD_TYPE_RESP_NAK,
+ SVDM_HDR_OBJ_POS(vdm_hdr), NULL, 0);
+ }
+ break;
+
+ case SVDM_CMD_TYPE_RESP_ACK:
+ if (svid != USBPD_SID) {
+ usbpd_err(&pd->dev, "unhandled ACK for SVID:0x%x\n",
+ svid);
+ break;
+ }
+
+ switch (cmd) {
+ case USBPD_SVDM_DISCOVER_IDENTITY:
+ kfree(pd->vdm_tx_retry);
+ pd->vdm_tx_retry = NULL;
+
+ pd->vdm_state = DISCOVERED_ID;
+ usbpd_send_svdm(pd, USBPD_SID,
+ USBPD_SVDM_DISCOVER_SVIDS,
+ SVDM_CMD_TYPE_INITIATOR, 0, NULL, 0);
+ break;
+
+ case USBPD_SVDM_DISCOVER_SVIDS:
+ pd->vdm_state = DISCOVERED_SVIDS;
+
+ kfree(pd->vdm_tx_retry);
+ pd->vdm_tx_retry = NULL;
+
+ if (!pd->discovered_svids) {
+ pd->num_svids = 2 * num_vdos;
+ pd->discovered_svids = kcalloc(pd->num_svids,
+ sizeof(u16),
+ GFP_KERNEL);
+ if (!pd->discovered_svids) {
+ usbpd_err(&pd->dev, "unable to allocate SVIDs\n");
+ break;
+ }
+
+ psvid = pd->discovered_svids;
+ } else { /* handle > 12 SVIDs */
+ void *ptr;
+ size_t oldsize = pd->num_svids * sizeof(u16);
+ size_t newsize = oldsize +
+ (2 * num_vdos * sizeof(u16));
+
+ ptr = krealloc(pd->discovered_svids, newsize,
+ GFP_KERNEL);
+ if (!ptr) {
+ usbpd_err(&pd->dev, "unable to realloc SVIDs\n");
+ break;
+ }
+
+ pd->discovered_svids = ptr;
+ psvid = pd->discovered_svids + pd->num_svids;
+ memset(psvid, 0, (2 * num_vdos));
+ pd->num_svids += 2 * num_vdos;
+ }
+
+ /* convert 32-bit VDOs to list of 16-bit SVIDs */
+ for (i = 0; i < num_vdos * 2; i++) {
+ /*
+ * Within each 32-bit VDO,
+ * SVID[i]: upper 16-bits
+ * SVID[i+1]: lower 16-bits
+ * where i is even.
+ */
+ if (!(i & 1))
+ svid = vdos[i >> 1] >> 16;
+ else
+ svid = vdos[i >> 1] & 0xFFFF;
+
+ /*
+ * There are some devices that incorrectly
+ * swap the order of SVIDs within a VDO. So in
+ * case of an odd-number of SVIDs it could end
+ * up with SVID[i] as 0 while SVID[i+1] is
+ * non-zero. Just skip over the zero ones.
+ */
+ if (svid) {
+ usbpd_dbg(&pd->dev, "Discovered SVID: 0x%04x\n",
+ svid);
+ *psvid++ = svid;
+ }
+ }
+
+ /* if more than 12 SVIDs, resend the request */
+ if (num_vdos == 6 && vdos[5] != 0) {
+ usbpd_send_svdm(pd, USBPD_SID,
+ USBPD_SVDM_DISCOVER_SVIDS,
+ SVDM_CMD_TYPE_INITIATOR, 0,
+ NULL, 0);
+ break;
+ }
+
+ /* now that all SVIDs are discovered, notify handlers */
+ for (i = 0; i < pd->num_svids; i++) {
+ svid = pd->discovered_svids[i];
+ if (svid) {
+ handler = find_svid_handler(pd, svid);
+ if (handler) {
+ handler->connect(handler);
+ handler->discovered = true;
+ }
+ }
+
+ if (svid == 0xFF01)
+ has_dp = true;
+ }
+
+ /*
+ * Finally start USB host now that we have determined
+ * if DisplayPort mode is present or not and limit USB
+ * to HS-only mode if so.
+ */
+ start_usb_host(pd, !has_dp);
+
+ break;
+
+ default:
+ usbpd_dbg(&pd->dev, "unhandled ACK for command:0x%x\n",
+ cmd);
+ break;
+ }
+ break;
+
+ case SVDM_CMD_TYPE_RESP_NAK:
+ usbpd_info(&pd->dev, "VDM NAK received for SVID:0x%04x command:0x%x\n",
+ svid, cmd);
+
+ switch (cmd) {
+ case USBPD_SVDM_DISCOVER_IDENTITY:
+ case USBPD_SVDM_DISCOVER_SVIDS:
+ start_usb_host(pd, true);
+ break;
+ default:
+ break;
+ }
+
+ break;
+
+ case SVDM_CMD_TYPE_RESP_BUSY:
+ switch (cmd) {
+ case USBPD_SVDM_DISCOVER_IDENTITY:
+ case USBPD_SVDM_DISCOVER_SVIDS:
+ if (!pd->vdm_tx_retry) {
+ usbpd_err(&pd->dev, "Discover command %d VDM was unexpectedly freed\n",
+ cmd);
+ break;
+ }
+
+ /* wait tVDMBusy, then retry */
+ pd->vdm_tx = pd->vdm_tx_retry;
+ pd->vdm_tx_retry = NULL;
+ kick_sm(pd, VDM_BUSY_TIME);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+}
+
+static void handle_vdm_tx(struct usbpd *pd)
+{
+ int ret;
+ unsigned long flags;
+
+ /* only send one VDM at a time */
+ if (pd->vdm_tx) {
+ u32 vdm_hdr = pd->vdm_tx->data[0];
+
+ /* bail out and try again later if a message just arrived */
+ spin_lock_irqsave(&pd->rx_lock, flags);
+ if (!list_empty(&pd->rx_q)) {
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+
+ ret = pd_send_msg(pd, MSG_VDM, pd->vdm_tx->data,
+ pd->vdm_tx->size, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error (%d) sending VDM command %d\n",
+ ret, SVDM_HDR_CMD(pd->vdm_tx->data[0]));
+
+ /* retry when hitting PE_SRC/SNK_Ready again */
+ if (ret != -EBUSY)
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_SEND_SOFT_RESET :
+ PE_SNK_SEND_SOFT_RESET);
+
+ return;
+ }
+
+ /*
+ * special case: keep initiated Discover ID/SVIDs
+ * around in case we need to re-try when receiving BUSY
+ */
+ if (VDM_IS_SVDM(vdm_hdr) &&
+ SVDM_HDR_CMD_TYPE(vdm_hdr) == SVDM_CMD_TYPE_INITIATOR &&
+ SVDM_HDR_CMD(vdm_hdr) <= USBPD_SVDM_DISCOVER_SVIDS) {
+ if (pd->vdm_tx_retry) {
+ usbpd_dbg(&pd->dev, "Previous Discover VDM command %d not ACKed/NAKed\n",
+ SVDM_HDR_CMD(
+ pd->vdm_tx_retry->data[0]));
+ kfree(pd->vdm_tx_retry);
+ }
+ pd->vdm_tx_retry = pd->vdm_tx;
+ } else {
+ kfree(pd->vdm_tx);
+ }
+
+ pd->vdm_tx = NULL;
+ }
+}
+
+static void reset_vdm_state(struct usbpd *pd)
+{
+ struct usbpd_svid_handler *handler;
+
+ list_for_each_entry(handler, &pd->svid_handlers, entry) {
+ if (handler->discovered) {
+ handler->disconnect(handler);
+ handler->discovered = false;
+ }
+ }
+
+ pd->vdm_state = VDM_NONE;
+ kfree(pd->vdm_tx_retry);
+ pd->vdm_tx_retry = NULL;
+ kfree(pd->discovered_svids);
+ pd->discovered_svids = NULL;
+ pd->num_svids = 0;
+ kfree(pd->vdm_tx);
+ pd->vdm_tx = NULL;
+}
+
+static void dr_swap(struct usbpd *pd)
+{
+ reset_vdm_state(pd);
+
+ if (pd->current_dr == DR_DFP) {
+ stop_usb_host(pd);
+ start_usb_peripheral(pd);
+ pd->current_dr = DR_UFP;
+ } else if (pd->current_dr == DR_UFP) {
+ stop_usb_peripheral(pd);
+ pd->current_dr = DR_DFP;
+
+ /* don't start USB host until after SVDM discovery */
+ usbpd_send_svdm(pd, USBPD_SID, USBPD_SVDM_DISCOVER_IDENTITY,
+ SVDM_CMD_TYPE_INITIATOR, 0, NULL, 0);
+ }
+
+ pd_phy_update_roles(pd->current_dr, pd->current_pr);
+ dual_role_instance_changed(pd->dual_role);
+}
+
+
+static void vconn_swap(struct usbpd *pd)
+{
+ int ret;
+
+ if (pd->vconn_enabled) {
+ pd->current_state = PE_VCS_WAIT_FOR_VCONN;
+ kick_sm(pd, VCONN_ON_TIME);
+ } else {
+ ret = regulator_enable(pd->vconn);
+ if (ret) {
+ usbpd_err(&pd->dev, "Unable to enable vconn\n");
+ return;
+ }
+
+ pd->vconn_enabled = true;
+
+ /*
+ * Small delay to ensure Vconn has ramped up. This is well
+ * below tVCONNSourceOn (100ms) so we still send PS_RDY within
+ * the allowed time.
+ */
+ usleep_range(5000, 10000);
+
+ ret = pd_send_msg(pd, MSG_PS_RDY, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending PS_RDY\n");
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_SEND_SOFT_RESET :
+ PE_SNK_SEND_SOFT_RESET);
+ return;
+ }
+ }
+}
+
+static int enable_vbus(struct usbpd *pd)
+{
+ union power_supply_propval val = {0};
+ int count = 100;
+ int ret;
+
+ if (!check_vsafe0v)
+ goto enable_reg;
+
+ /*
+ * Check to make sure there's no lingering charge on
+ * VBUS before enabling it as a source. If so poll here
+ * until it goes below VSafe0V (0.8V) before proceeding.
+ */
+ while (count--) {
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
+ if (ret || val.intval <= 800000)
+ break;
+ usleep_range(20000, 30000);
+ }
+
+ if (count < 99)
+ msleep(100); /* need to wait an additional tCCDebounce */
+
+enable_reg:
+ ret = regulator_enable(pd->vbus);
+ if (ret)
+ usbpd_err(&pd->dev, "Unable to enable vbus (%d)\n", ret);
+ else
+ pd->vbus_enabled = true;
+
+ return ret;
+}
+
+static inline void rx_msg_cleanup(struct usbpd *pd)
+{
+ struct rx_msg *msg, *tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pd->rx_lock, flags);
+ list_for_each_entry_safe(msg, tmp, &pd->rx_q, entry) {
+ list_del(&msg->entry);
+ kfree(msg);
+ }
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+}
+
+/* For PD 3.0, check SinkTxOk before allowing initiating AMS */
+static inline bool is_sink_tx_ok(struct usbpd *pd)
+{
+ if (pd->spec_rev == USBPD_REV_30)
+ return pd->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH;
+
+ return true;
+}
+
+/* Handles current state and determines transitions */
+static void usbpd_sm(struct work_struct *w)
+{
+ struct usbpd *pd = container_of(w, struct usbpd, sm_work);
+ union power_supply_propval val = {0};
+ int ret;
+ struct rx_msg *rx_msg = NULL;
+ unsigned long flags;
+
+ usbpd_dbg(&pd->dev, "handle state %s\n",
+ usbpd_state_strings[pd->current_state]);
+
+ hrtimer_cancel(&pd->timer);
+ pd->sm_queued = false;
+
+ spin_lock_irqsave(&pd->rx_lock, flags);
+ if (!list_empty(&pd->rx_q)) {
+ rx_msg = list_first_entry(&pd->rx_q, struct rx_msg, entry);
+ list_del(&rx_msg->entry);
+ }
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+
+ /* Disconnect? */
+ if (pd->current_pr == PR_NONE) {
+ if (pd->current_state == PE_UNKNOWN)
+ goto sm_done;
+
+ if (pd->vconn_enabled) {
+ regulator_disable(pd->vconn);
+ pd->vconn_enabled = false;
+ }
+
+ usbpd_info(&pd->dev, "USB Type-C disconnect\n");
+
+ if (pd->pd_phy_opened) {
+ pd_phy_close();
+ pd->pd_phy_opened = false;
+ }
+
+ pd->in_pr_swap = false;
+ pd->pd_connected = false;
+ pd->in_explicit_contract = false;
+ pd->hard_reset_recvd = false;
+ pd->caps_count = 0;
+ pd->hard_reset_count = 0;
+ pd->requested_voltage = 0;
+ pd->requested_current = 0;
+ pd->selected_pdo = pd->requested_pdo = 0;
+ memset(&pd->received_pdos, 0, sizeof(pd->received_pdos));
+ rx_msg_cleanup(pd);
+
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_IN_HARD_RESET, &val);
+
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED,
+ &val);
+
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ACTIVE, &val);
+
+ if (pd->vbus_enabled) {
+ regulator_disable(pd->vbus);
+ pd->vbus_enabled = false;
+ }
+
+ if (pd->current_dr == DR_UFP)
+ stop_usb_peripheral(pd);
+ else if (pd->current_dr == DR_DFP)
+ stop_usb_host(pd);
+
+ pd->current_dr = DR_NONE;
+
+ reset_vdm_state(pd);
+
+ if (pd->current_state == PE_ERROR_RECOVERY)
+ /* forced disconnect, wait before resetting to DRP */
+ usleep_range(ERROR_RECOVERY_TIME * USEC_PER_MSEC,
+ (ERROR_RECOVERY_TIME + 5) * USEC_PER_MSEC);
+
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PR_SWAP, &val);
+
+ /* set due to dual_role class "mode" change */
+ if (pd->forced_pr != POWER_SUPPLY_TYPEC_PR_NONE)
+ val.intval = pd->forced_pr;
+ else
+ /* Set CC back to DRP toggle */
+ val.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
+
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &val);
+ pd->forced_pr = POWER_SUPPLY_TYPEC_PR_NONE;
+
+ pd->current_state = PE_UNKNOWN;
+
+ kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
+ dual_role_instance_changed(pd->dual_role);
+
+ goto sm_done;
+ }
+
+ /* Hard reset? */
+ if (pd->hard_reset_recvd) {
+ pd->hard_reset_recvd = false;
+
+ if (pd->requested_current) {
+ val.intval = pd->requested_current = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_CURRENT_MAX, &val);
+ }
+
+ pd->requested_voltage = 5000000;
+ val.intval = pd->requested_voltage;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MIN, &val);
+
+ pd->in_pr_swap = false;
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PR_SWAP, &val);
+
+ pd->in_explicit_contract = false;
+ pd->selected_pdo = pd->requested_pdo = 0;
+ pd->rdo = 0;
+ rx_msg_cleanup(pd);
+ reset_vdm_state(pd);
+ kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
+
+ if (pd->current_pr == PR_SINK) {
+ usbpd_set_state(pd, PE_SNK_TRANSITION_TO_DEFAULT);
+ } else {
+ pd->current_state = PE_SRC_TRANSITION_TO_DEFAULT;
+ kick_sm(pd, PS_HARD_RESET_TIME);
+ }
+
+ goto sm_done;
+ }
+
+ /* Soft reset? */
+ if (IS_CTRL(rx_msg, MSG_SOFT_RESET)) {
+ usbpd_dbg(&pd->dev, "Handle soft reset\n");
+
+ if (pd->current_pr == PR_SRC)
+ pd->current_state = PE_SRC_SOFT_RESET;
+ else if (pd->current_pr == PR_SINK)
+ pd->current_state = PE_SNK_SOFT_RESET;
+ }
+
+ switch (pd->current_state) {
+ case PE_UNKNOWN:
+ if (pd->current_pr == PR_SINK) {
+ usbpd_set_state(pd, PE_SNK_STARTUP);
+ } else if (pd->current_pr == PR_SRC) {
+ if (!pd->vconn_enabled &&
+ pd->typec_mode ==
+ POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE) {
+ ret = regulator_enable(pd->vconn);
+ if (ret)
+ usbpd_err(&pd->dev, "Unable to enable vconn\n");
+ else
+ pd->vconn_enabled = true;
+ }
+ enable_vbus(pd);
+
+ usbpd_set_state(pd, PE_SRC_STARTUP);
+ }
+ break;
+
+ case PE_SRC_STARTUP:
+ usbpd_set_state(pd, PE_SRC_STARTUP);
+ break;
+
+ case PE_SRC_SEND_CAPABILITIES:
+ ret = pd_send_msg(pd, MSG_SOURCE_CAPABILITIES, default_src_caps,
+ ARRAY_SIZE(default_src_caps), SOP_MSG);
+ if (ret) {
+ pd->caps_count++;
+
+ if (pd->caps_count == 10 && pd->current_dr == DR_DFP) {
+ /* Likely not PD-capable, start host now */
+ start_usb_host(pd, true);
+ } else if (pd->caps_count >= PD_CAPS_COUNT) {
+ usbpd_dbg(&pd->dev, "Src CapsCounter exceeded, disabling PD\n");
+ usbpd_set_state(pd, PE_SRC_DISABLED);
+
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ACTIVE,
+ &val);
+ break;
+ }
+
+ kick_sm(pd, SRC_CAP_TIME);
+ break;
+ }
+
+ /* transmit was successful if GoodCRC was received */
+ pd->caps_count = 0;
+ pd->hard_reset_count = 0;
+ pd->pd_connected = true; /* we know peer is PD capable */
+
+ /* wait for REQUEST */
+ pd->current_state = PE_SRC_SEND_CAPABILITIES_WAIT;
+ kick_sm(pd, SENDER_RESPONSE_TIME);
+
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ACTIVE, &val);
+ break;
+
+ case PE_SRC_SEND_CAPABILITIES_WAIT:
+ if (IS_DATA(rx_msg, MSG_REQUEST)) {
+ pd->rdo = rx_msg->payload[0];
+ usbpd_set_state(pd, PE_SRC_NEGOTIATE_CAPABILITY);
+ } else if (rx_msg) {
+ usbpd_err(&pd->dev, "Unexpected message received\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ } else {
+ usbpd_set_state(pd, PE_SRC_HARD_RESET);
+ }
+ break;
+
+ case PE_SRC_READY:
+ if (IS_CTRL(rx_msg, MSG_GET_SOURCE_CAP)) {
+ pd->current_state = PE_SRC_SEND_CAPABILITIES;
+ kick_sm(pd, 0);
+ } else if (IS_CTRL(rx_msg, MSG_GET_SINK_CAP)) {
+ ret = pd_send_msg(pd, MSG_SINK_CAPABILITIES,
+ pd->sink_caps, pd->num_sink_caps,
+ SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Sink Caps\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ }
+ } else if (IS_DATA(rx_msg, MSG_REQUEST)) {
+ pd->rdo = rx_msg->payload[0];
+ usbpd_set_state(pd, PE_SRC_NEGOTIATE_CAPABILITY);
+ } else if (IS_CTRL(rx_msg, MSG_DR_SWAP)) {
+ if (pd->vdm_state == MODE_ENTERED) {
+ usbpd_set_state(pd, PE_SRC_HARD_RESET);
+ break;
+ }
+
+ ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Accept\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ dr_swap(pd);
+ } else if (IS_CTRL(rx_msg, MSG_PR_SWAP)) {
+ /* lock in current mode */
+ set_power_role(pd, pd->current_pr);
+
+ /* we'll happily accept Src->Sink requests anytime */
+ ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Accept\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ pd->current_state = PE_PRS_SRC_SNK_TRANSITION_TO_OFF;
+ kick_sm(pd, SRC_TRANSITION_TIME);
+ break;
+ } else if (IS_CTRL(rx_msg, MSG_VCONN_SWAP)) {
+ ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Accept\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ vconn_swap(pd);
+ } else if (IS_DATA(rx_msg, MSG_VDM)) {
+ handle_vdm_rx(pd, rx_msg);
+ } else if (pd->send_pr_swap) {
+ pd->send_pr_swap = false;
+ ret = pd_send_msg(pd, MSG_PR_SWAP, NULL, 0, SOP_MSG);
+ if (ret) {
+ dev_err(&pd->dev, "Error sending PR Swap\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ pd->current_state = PE_PRS_SRC_SNK_SEND_SWAP;
+ kick_sm(pd, SENDER_RESPONSE_TIME);
+ } else if (pd->send_dr_swap) {
+ pd->send_dr_swap = false;
+ ret = pd_send_msg(pd, MSG_DR_SWAP, NULL, 0, SOP_MSG);
+ if (ret) {
+ dev_err(&pd->dev, "Error sending DR Swap\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ pd->current_state = PE_DRS_SEND_DR_SWAP;
+ kick_sm(pd, SENDER_RESPONSE_TIME);
+ } else {
+ handle_vdm_tx(pd);
+ }
+ break;
+
+ case PE_SRC_TRANSITION_TO_DEFAULT:
+ if (pd->vconn_enabled)
+ regulator_disable(pd->vconn);
+ if (pd->vbus_enabled)
+ regulator_disable(pd->vbus);
+
+ if (pd->current_dr != DR_DFP) {
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB, 0);
+ pd->current_dr = DR_DFP;
+ pd_phy_update_roles(pd->current_dr, pd->current_pr);
+ }
+
+ msleep(SRC_RECOVER_TIME);
+
+ pd->vbus_enabled = false;
+ enable_vbus(pd);
+
+ if (pd->vconn_enabled) {
+ ret = regulator_enable(pd->vconn);
+ if (ret) {
+ usbpd_err(&pd->dev, "Unable to enable vconn\n");
+ pd->vconn_enabled = false;
+ }
+ }
+
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_IN_HARD_RESET, &val);
+
+ usbpd_set_state(pd, PE_SRC_STARTUP);
+ break;
+
+ case PE_SRC_HARD_RESET:
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_IN_HARD_RESET, &val);
+
+ pd_send_hard_reset(pd);
+ pd->in_explicit_contract = false;
+ pd->rdo = 0;
+ rx_msg_cleanup(pd);
+ reset_vdm_state(pd);
+ kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
+
+ pd->current_state = PE_SRC_TRANSITION_TO_DEFAULT;
+ kick_sm(pd, PS_HARD_RESET_TIME);
+ break;
+
+ case PE_SNK_STARTUP:
+ usbpd_set_state(pd, PE_SNK_STARTUP);
+ break;
+
+ case PE_SNK_DISCOVERY:
+ if (!rx_msg) {
+ if (pd->vbus_present)
+ usbpd_set_state(pd,
+ PE_SNK_WAIT_FOR_CAPABILITIES);
+
+ /*
+ * Handle disconnection in the middle of PR_Swap.
+ * Since in psy_changed() if pd->in_pr_swap is true
+ * we ignore the typec_mode==NONE change since that is
+ * expected to happen. However if the cable really did
+ * get disconnected we need to check for it here after
+ * waiting for VBUS presence times out.
+ */
+ if (!pd->typec_mode) {
+ pd->current_pr = PR_NONE;
+ kick_sm(pd, 0);
+ }
+
+ break;
+ }
+ /* else fall-through */
+
+ case PE_SNK_WAIT_FOR_CAPABILITIES:
+ pd->in_pr_swap = false;
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PR_SWAP, &val);
+
+ if (IS_DATA(rx_msg, MSG_SOURCE_CAPABILITIES)) {
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_IN_HARD_RESET,
+ &val);
+
+ /* save the PDOs so userspace can further evaluate */
+ memcpy(&pd->received_pdos, rx_msg->payload,
+ sizeof(pd->received_pdos));
+ pd->src_cap_id++;
+
+ usbpd_set_state(pd, PE_SNK_EVALUATE_CAPABILITY);
+
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ACTIVE, &val);
+ } else if (pd->hard_reset_count < 3) {
+ usbpd_set_state(pd, PE_SNK_HARD_RESET);
+ } else {
+ usbpd_dbg(&pd->dev, "Sink hard reset count exceeded, disabling PD\n");
+
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_IN_HARD_RESET,
+ &val);
+
+ val.intval = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ACTIVE, &val);
+
+ pd_phy_close();
+ pd->pd_phy_opened = false;
+ }
+ break;
+
+ case PE_SNK_SELECT_CAPABILITY:
+ if (IS_CTRL(rx_msg, MSG_ACCEPT)) {
+ u32 pdo = pd->received_pdos[pd->requested_pdo - 1];
+ bool same_pps = (pd->selected_pdo == pd->requested_pdo)
+ && (PD_SRC_PDO_TYPE(pdo) ==
+ PD_SRC_PDO_TYPE_AUGMENTED);
+
+ usbpd_set_state(pd, PE_SNK_TRANSITION_SINK);
+
+ /* prepare for voltage increase/decrease */
+ val.intval = pd->requested_voltage;
+ power_supply_set_property(pd->usb_psy,
+ pd->requested_voltage >= pd->current_voltage ?
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MAX :
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MIN,
+ &val);
+
+ /*
+ * if changing voltages (not within the same PPS PDO),
+ * we must lower input current to pSnkStdby (2.5W).
+ * Calculate it and set PD_CURRENT_MAX accordingly.
+ */
+ if (!same_pps &&
+ pd->requested_voltage != pd->current_voltage) {
+ int mv = max(pd->requested_voltage,
+ pd->current_voltage) / 1000;
+ val.intval = (2500000 / mv) * 1000;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_CURRENT_MAX, &val);
+ } else {
+ /* decreasing current? */
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_CURRENT_MAX, &val);
+ if (!ret &&
+ pd->requested_current < val.intval) {
+ val.intval =
+ pd->requested_current * 1000;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_CURRENT_MAX,
+ &val);
+ }
+ }
+
+ pd->selected_pdo = pd->requested_pdo;
+ } else if (IS_CTRL(rx_msg, MSG_REJECT) ||
+ IS_CTRL(rx_msg, MSG_WAIT)) {
+ if (pd->in_explicit_contract)
+ usbpd_set_state(pd, PE_SNK_READY);
+ else
+ usbpd_set_state(pd,
+ PE_SNK_WAIT_FOR_CAPABILITIES);
+ } else if (rx_msg) {
+ usbpd_err(&pd->dev, "Invalid response to sink request\n");
+ usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
+ } else {
+ /* timed out; go to hard reset */
+ usbpd_set_state(pd, PE_SNK_HARD_RESET);
+ }
+ break;
+
+ case PE_SNK_TRANSITION_SINK:
+ if (IS_CTRL(rx_msg, MSG_PS_RDY)) {
+ val.intval = pd->requested_voltage;
+ power_supply_set_property(pd->usb_psy,
+ pd->requested_voltage >= pd->current_voltage ?
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MIN :
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MAX, &val);
+ pd->current_voltage = pd->requested_voltage;
+
+ /* resume charging */
+ val.intval = pd->requested_current * 1000; /* mA->uA */
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_CURRENT_MAX, &val);
+
+ usbpd_set_state(pd, PE_SNK_READY);
+ } else {
+ /* timed out; go to hard reset */
+ usbpd_set_state(pd, PE_SNK_HARD_RESET);
+ }
+ break;
+
+ case PE_SNK_READY:
+ if (IS_DATA(rx_msg, MSG_SOURCE_CAPABILITIES)) {
+ /* save the PDOs so userspace can further evaluate */
+ memcpy(&pd->received_pdos, rx_msg->payload,
+ sizeof(pd->received_pdos));
+ pd->src_cap_id++;
+
+ usbpd_set_state(pd, PE_SNK_EVALUATE_CAPABILITY);
+ } else if (IS_CTRL(rx_msg, MSG_GET_SINK_CAP)) {
+ ret = pd_send_msg(pd, MSG_SINK_CAPABILITIES,
+ pd->sink_caps, pd->num_sink_caps,
+ SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Sink Caps\n");
+ usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
+ }
+ } else if (IS_CTRL(rx_msg, MSG_GET_SOURCE_CAP)) {
+ ret = pd_send_msg(pd, MSG_SOURCE_CAPABILITIES,
+ default_src_caps,
+ ARRAY_SIZE(default_src_caps), SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending SRC CAPs\n");
+ usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
+ break;
+ }
+ } else if (IS_CTRL(rx_msg, MSG_DR_SWAP)) {
+ if (pd->vdm_state == MODE_ENTERED) {
+ usbpd_set_state(pd, PE_SNK_HARD_RESET);
+ break;
+ }
+
+ ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Accept\n");
+ usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
+ break;
+ }
+
+ dr_swap(pd);
+ } else if (IS_CTRL(rx_msg, MSG_PR_SWAP)) {
+ /* lock in current mode */
+ set_power_role(pd, pd->current_pr);
+
+ /* TODO: should we Reject in certain circumstances? */
+ ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Accept\n");
+ usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
+ break;
+ }
+
+ pd->in_pr_swap = true;
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PR_SWAP, &val);
+ usbpd_set_state(pd, PE_PRS_SNK_SRC_TRANSITION_TO_OFF);
+ break;
+ } else if (IS_CTRL(rx_msg, MSG_VCONN_SWAP)) {
+ /*
+ * if VCONN is connected to VBUS, make sure we are
+ * not in high voltage contract, otherwise reject.
+ */
+ if (!pd->vconn_is_external &&
+ (pd->requested_voltage > 5000000)) {
+ ret = pd_send_msg(pd, MSG_REJECT, NULL, 0,
+ SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Reject\n");
+ usbpd_set_state(pd,
+ PE_SNK_SEND_SOFT_RESET);
+ }
+
+ break;
+ }
+
+ ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending Accept\n");
+ usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
+ break;
+ }
+
+ vconn_swap(pd);
+ } else if (IS_DATA(rx_msg, MSG_VDM)) {
+ handle_vdm_rx(pd, rx_msg);
+ } else if (pd->send_request) {
+ pd->send_request = false;
+ usbpd_set_state(pd, PE_SNK_SELECT_CAPABILITY);
+ } else if (pd->send_pr_swap && is_sink_tx_ok(pd)) {
+ pd->send_pr_swap = false;
+ ret = pd_send_msg(pd, MSG_PR_SWAP, NULL, 0, SOP_MSG);
+ if (ret) {
+ dev_err(&pd->dev, "Error sending PR Swap\n");
+ usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
+ break;
+ }
+
+ pd->current_state = PE_PRS_SNK_SRC_SEND_SWAP;
+ kick_sm(pd, SENDER_RESPONSE_TIME);
+ } else if (pd->send_dr_swap && is_sink_tx_ok(pd)) {
+ pd->send_dr_swap = false;
+ ret = pd_send_msg(pd, MSG_DR_SWAP, NULL, 0, SOP_MSG);
+ if (ret) {
+ dev_err(&pd->dev, "Error sending DR Swap\n");
+ usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
+ break;
+ }
+
+ pd->current_state = PE_DRS_SEND_DR_SWAP;
+ kick_sm(pd, SENDER_RESPONSE_TIME);
+ } else if (is_sink_tx_ok(pd)) {
+ handle_vdm_tx(pd);
+ }
+ break;
+
+ case PE_SNK_TRANSITION_TO_DEFAULT:
+ usbpd_set_state(pd, PE_SNK_STARTUP);
+ break;
+
+ case PE_SRC_SOFT_RESET:
+ case PE_SNK_SOFT_RESET:
+ pd_reset_protocol(pd);
+
+ ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "%s: Error sending Accept, do Hard Reset\n",
+ usbpd_state_strings[pd->current_state]);
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_HARD_RESET : PE_SNK_HARD_RESET);
+ break;
+ }
+
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_SEND_CAPABILITIES :
+ PE_SNK_WAIT_FOR_CAPABILITIES);
+ break;
+
+ case PE_SRC_SEND_SOFT_RESET:
+ case PE_SNK_SEND_SOFT_RESET:
+ if (IS_CTRL(rx_msg, MSG_ACCEPT)) {
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_SEND_CAPABILITIES :
+ PE_SNK_WAIT_FOR_CAPABILITIES);
+ } else {
+ usbpd_err(&pd->dev, "%s: Did not see Accept, do Hard Reset\n",
+ usbpd_state_strings[pd->current_state]);
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_HARD_RESET : PE_SNK_HARD_RESET);
+ }
+ break;
+
+ case PE_SNK_HARD_RESET:
+ /* prepare charger for VBUS change */
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_IN_HARD_RESET, &val);
+
+ pd->requested_voltage = 5000000;
+
+ if (pd->requested_current) {
+ val.intval = pd->requested_current = 0;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_CURRENT_MAX, &val);
+ }
+
+ val.intval = pd->requested_voltage;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MIN, &val);
+
+ pd_send_hard_reset(pd);
+ pd->in_explicit_contract = false;
+ pd->selected_pdo = pd->requested_pdo = 0;
+ pd->rdo = 0;
+ reset_vdm_state(pd);
+ kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
+ usbpd_set_state(pd, PE_SNK_TRANSITION_TO_DEFAULT);
+ break;
+
+ case PE_DRS_SEND_DR_SWAP:
+ if (IS_CTRL(rx_msg, MSG_ACCEPT))
+ dr_swap(pd);
+
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_READY : PE_SNK_READY);
+ break;
+
+ case PE_PRS_SRC_SNK_SEND_SWAP:
+ if (!IS_CTRL(rx_msg, MSG_ACCEPT)) {
+ pd->current_state = PE_SRC_READY;
+ break;
+ }
+
+ pd->current_state = PE_PRS_SRC_SNK_TRANSITION_TO_OFF;
+ kick_sm(pd, SRC_TRANSITION_TIME);
+ break;
+
+ case PE_PRS_SRC_SNK_TRANSITION_TO_OFF:
+ pd->in_pr_swap = true;
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PR_SWAP, &val);
+ pd->in_explicit_contract = false;
+
+ if (pd->vbus_enabled) {
+ regulator_disable(pd->vbus);
+ pd->vbus_enabled = false;
+ }
+
+ /* PE_PRS_SRC_SNK_Assert_Rd */
+ pd->current_pr = PR_SINK;
+ set_power_role(pd, pd->current_pr);
+ pd_phy_update_roles(pd->current_dr, pd->current_pr);
+
+ /* allow time for Vbus discharge, must be < tSrcSwapStdby */
+ msleep(500);
+
+ ret = pd_send_msg(pd, MSG_PS_RDY, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending PS_RDY\n");
+ usbpd_set_state(pd, PE_ERROR_RECOVERY);
+ break;
+ }
+
+ pd->current_state = PE_PRS_SRC_SNK_WAIT_SOURCE_ON;
+ kick_sm(pd, PS_SOURCE_ON);
+ break;
+
+ case PE_PRS_SRC_SNK_WAIT_SOURCE_ON:
+ if (IS_CTRL(rx_msg, MSG_PS_RDY))
+ usbpd_set_state(pd, PE_SNK_STARTUP);
+ else
+ usbpd_set_state(pd, PE_ERROR_RECOVERY);
+ break;
+
+ case PE_PRS_SNK_SRC_SEND_SWAP:
+ if (!IS_CTRL(rx_msg, MSG_ACCEPT)) {
+ pd->current_state = PE_SNK_READY;
+ break;
+ }
+
+ pd->in_pr_swap = true;
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PR_SWAP, &val);
+ usbpd_set_state(pd, PE_PRS_SNK_SRC_TRANSITION_TO_OFF);
+ break;
+
+ case PE_PRS_SNK_SRC_TRANSITION_TO_OFF:
+ if (!IS_CTRL(rx_msg, MSG_PS_RDY)) {
+ usbpd_set_state(pd, PE_ERROR_RECOVERY);
+ break;
+ }
+
+ /* PE_PRS_SNK_SRC_Assert_Rp */
+ pd->current_pr = PR_SRC;
+ set_power_role(pd, pd->current_pr);
+ pd->current_state = PE_PRS_SNK_SRC_SOURCE_ON;
+
+ /* fall-through */
+
+ case PE_PRS_SNK_SRC_SOURCE_ON:
+ enable_vbus(pd);
+ msleep(200); /* allow time VBUS ramp-up, must be < tNewSrc */
+
+ ret = pd_send_msg(pd, MSG_PS_RDY, NULL, 0, SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending PS_RDY\n");
+ usbpd_set_state(pd, PE_ERROR_RECOVERY);
+ break;
+ }
+
+ usbpd_set_state(pd, PE_SRC_STARTUP);
+ break;
+
+ case PE_VCS_WAIT_FOR_VCONN:
+ if (IS_CTRL(rx_msg, MSG_PS_RDY)) {
+ /*
+ * hopefully redundant check but in case not enabled
+ * avoids unbalanced regulator disable count
+ */
+ if (pd->vconn_enabled)
+ regulator_disable(pd->vconn);
+ pd->vconn_enabled = false;
+
+ pd->current_state = pd->current_pr == PR_SRC ?
+ PE_SRC_READY : PE_SNK_READY;
+ } else {
+ /* timed out; go to hard reset */
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_HARD_RESET : PE_SNK_HARD_RESET);
+ }
+
+ break;
+
+ default:
+ usbpd_err(&pd->dev, "Unhandled state %s\n",
+ usbpd_state_strings[pd->current_state]);
+ break;
+ }
+
+sm_done:
+ kfree(rx_msg);
+
+ spin_lock_irqsave(&pd->rx_lock, flags);
+ ret = list_empty(&pd->rx_q);
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+
+ /* requeue if there are any new/pending RX messages */
+ if (!ret)
+ kick_sm(pd, 0);
+
+ if (!pd->sm_queued)
+ pm_relax(&pd->dev);
+}
+
+static inline const char *src_current(enum power_supply_typec_mode typec_mode)
+{
+ switch (typec_mode) {
+ case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
+ return "default";
+ case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
+ return "medium - 1.5A";
+ case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
+ return "high - 3.0A";
+ default:
+ return "";
+ }
+}
+
+static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)
+{
+ struct usbpd *pd = container_of(nb, struct usbpd, psy_nb);
+ union power_supply_propval val;
+ enum power_supply_typec_mode typec_mode;
+ int ret;
+
+ if (ptr != pd->usb_psy || evt != PSY_EVENT_PROP_CHANGED)
+ return 0;
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_TYPEC_MODE, &val);
+ if (ret) {
+ usbpd_err(&pd->dev, "Unable to read USB TYPEC_MODE: %d\n", ret);
+ return ret;
+ }
+
+ typec_mode = val.intval;
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PE_START, &val);
+ if (ret) {
+ usbpd_err(&pd->dev, "Unable to read USB PROP_PE_START: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Don't proceed if PE_START=0 as other props may still change */
+ if (!val.intval && !pd->pd_connected &&
+ typec_mode != POWER_SUPPLY_TYPEC_NONE)
+ return 0;
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT, &val);
+ if (ret) {
+ usbpd_err(&pd->dev, "Unable to read USB PRESENT: %d\n", ret);
+ return ret;
+ }
+
+ pd->vbus_present = val.intval;
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_REAL_TYPE, &val);
+ if (ret) {
+ usbpd_err(&pd->dev, "Unable to read USB TYPE: %d\n", ret);
+ return ret;
+ }
+
+ pd->psy_type = val.intval;
+
+ /*
+ * For sink hard reset, state machine needs to know when VBUS changes
+ * - when in PE_SNK_TRANSITION_TO_DEFAULT, notify when VBUS falls
+ * - when in PE_SNK_DISCOVERY, notify when VBUS rises
+ */
+ if (typec_mode && ((!pd->vbus_present &&
+ pd->current_state == PE_SNK_TRANSITION_TO_DEFAULT) ||
+ (pd->vbus_present && pd->current_state == PE_SNK_DISCOVERY))) {
+ usbpd_dbg(&pd->dev, "hard reset: typec mode:%d present:%d\n",
+ typec_mode, pd->vbus_present);
+ pd->typec_mode = typec_mode;
+ kick_sm(pd, 0);
+ return 0;
+ }
+
+ if (pd->typec_mode == typec_mode)
+ return 0;
+
+ pd->typec_mode = typec_mode;
+
+ usbpd_dbg(&pd->dev, "typec mode:%d present:%d type:%d orientation:%d\n",
+ typec_mode, pd->vbus_present, pd->psy_type,
+ usbpd_get_plug_orientation(pd));
+
+ switch (typec_mode) {
+ /* Disconnect */
+ case POWER_SUPPLY_TYPEC_NONE:
+ if (pd->in_pr_swap) {
+ usbpd_dbg(&pd->dev, "Ignoring disconnect due to PR swap\n");
+ return 0;
+ }
+
+ pd->current_pr = PR_NONE;
+ break;
+
+ /* Sink states */
+ case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
+ case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
+ case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
+ usbpd_info(&pd->dev, "Type-C Source (%s) connected\n",
+ src_current(typec_mode));
+
+ /* if waiting for SinkTxOk to start an AMS */
+ if (pd->spec_rev == USBPD_REV_30 &&
+ typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH &&
+ (pd->send_pr_swap || pd->send_dr_swap || pd->vdm_tx))
+ break;
+
+ if (pd->current_pr == PR_SINK)
+ return 0;
+
+ /*
+ * Unexpected if not in PR swap; need to force disconnect from
+ * source so we can turn off VBUS, Vconn, PD PHY etc.
+ */
+ if (pd->current_pr == PR_SRC) {
+ usbpd_info(&pd->dev, "Forcing disconnect from source mode\n");
+ pd->current_pr = PR_NONE;
+ break;
+ }
+
+ pd->current_pr = PR_SINK;
+ break;
+
+ /* Source states */
+ case POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE:
+ case POWER_SUPPLY_TYPEC_SINK:
+ usbpd_info(&pd->dev, "Type-C Sink%s connected\n",
+ typec_mode == POWER_SUPPLY_TYPEC_SINK ?
+ "" : " (powered)");
+
+ if (pd->current_pr == PR_SRC)
+ return 0;
+
+ pd->current_pr = PR_SRC;
+ break;
+
+ case POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY:
+ usbpd_info(&pd->dev, "Type-C Debug Accessory connected\n");
+ break;
+ case POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER:
+ usbpd_info(&pd->dev, "Type-C Analog Audio Adapter connected\n");
+ break;
+ default:
+ usbpd_warn(&pd->dev, "Unsupported typec mode:%d\n",
+ typec_mode);
+ break;
+ }
+
+ /* queue state machine due to CC state change */
+ kick_sm(pd, 0);
+ return 0;
+}
+
+static enum dual_role_property usbpd_dr_properties[] = {
+ DUAL_ROLE_PROP_SUPPORTED_MODES,
+ DUAL_ROLE_PROP_MODE,
+ DUAL_ROLE_PROP_PR,
+ DUAL_ROLE_PROP_DR,
+};
+
+static int usbpd_dr_get_property(struct dual_role_phy_instance *dual_role,
+ enum dual_role_property prop, unsigned int *val)
+{
+ struct usbpd *pd = dual_role_get_drvdata(dual_role);
+
+ if (!pd)
+ return -ENODEV;
+
+ switch (prop) {
+ case DUAL_ROLE_PROP_MODE:
+ /* For now associate UFP/DFP with data role only */
+ if (pd->current_dr == DR_UFP)
+ *val = DUAL_ROLE_PROP_MODE_UFP;
+ else if (pd->current_dr == DR_DFP)
+ *val = DUAL_ROLE_PROP_MODE_DFP;
+ else
+ *val = DUAL_ROLE_PROP_MODE_NONE;
+ break;
+ case DUAL_ROLE_PROP_PR:
+ if (pd->current_pr == PR_SRC)
+ *val = DUAL_ROLE_PROP_PR_SRC;
+ else if (pd->current_pr == PR_SINK)
+ *val = DUAL_ROLE_PROP_PR_SNK;
+ else
+ *val = DUAL_ROLE_PROP_PR_NONE;
+ break;
+ case DUAL_ROLE_PROP_DR:
+ if (pd->current_dr == DR_UFP)
+ *val = DUAL_ROLE_PROP_DR_DEVICE;
+ else if (pd->current_dr == DR_DFP)
+ *val = DUAL_ROLE_PROP_DR_HOST;
+ else
+ *val = DUAL_ROLE_PROP_DR_NONE;
+ break;
+ default:
+ usbpd_warn(&pd->dev, "unsupported property %d\n", prop);
+ return -ENODATA;
+ }
+
+ return 0;
+}
+
+static int usbpd_dr_set_property(struct dual_role_phy_instance *dual_role,
+ enum dual_role_property prop, const unsigned int *val)
+{
+ struct usbpd *pd = dual_role_get_drvdata(dual_role);
+ bool do_swap = false;
+ int wait_count = 5;
+
+ if (!pd)
+ return -ENODEV;
+
+ switch (prop) {
+ case DUAL_ROLE_PROP_MODE:
+ usbpd_dbg(&pd->dev, "Setting mode to %d\n", *val);
+
+ if (pd->current_state == PE_UNKNOWN) {
+ usbpd_warn(&pd->dev, "No active connection. Don't allow MODE change\n");
+ return -EAGAIN;
+ }
+
+ /*
+ * Forces disconnect on CC and re-establishes connection.
+ * This does not use PD-based PR/DR swap
+ */
+ if (*val == DUAL_ROLE_PROP_MODE_UFP)
+ pd->forced_pr = POWER_SUPPLY_TYPEC_PR_SINK;
+ else if (*val == DUAL_ROLE_PROP_MODE_DFP)
+ pd->forced_pr = POWER_SUPPLY_TYPEC_PR_SOURCE;
+
+ /* new mode will be applied in disconnect handler */
+ set_power_role(pd, PR_NONE);
+
+ /* wait until it takes effect */
+ while (pd->forced_pr != POWER_SUPPLY_TYPEC_PR_NONE &&
+ --wait_count)
+ msleep(20);
+
+ if (!wait_count) {
+ usbpd_err(&pd->dev, "setting mode timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ break;
+
+ case DUAL_ROLE_PROP_DR:
+ usbpd_dbg(&pd->dev, "Setting data_role to %d\n", *val);
+
+ if (*val == DUAL_ROLE_PROP_DR_HOST) {
+ if (pd->current_dr == DR_UFP)
+ do_swap = true;
+ } else if (*val == DUAL_ROLE_PROP_DR_DEVICE) {
+ if (pd->current_dr == DR_DFP)
+ do_swap = true;
+ } else {
+ usbpd_warn(&pd->dev, "setting data_role to 'none' unsupported\n");
+ return -ENOTSUPP;
+ }
+
+ if (do_swap) {
+ if (pd->current_state != PE_SRC_READY &&
+ pd->current_state != PE_SNK_READY) {
+ usbpd_err(&pd->dev, "data_role swap not allowed: PD not in Ready state\n");
+ return -EAGAIN;
+ }
+
+ if (pd->current_state == PE_SNK_READY &&
+ !is_sink_tx_ok(pd)) {
+ usbpd_err(&pd->dev, "Rp indicates SinkTxNG\n");
+ return -EAGAIN;
+ }
+
+ mutex_lock(&pd->swap_lock);
+ reinit_completion(&pd->is_ready);
+ pd->send_dr_swap = true;
+ kick_sm(pd, 0);
+
+ /* wait for operation to complete */
+ if (!wait_for_completion_timeout(&pd->is_ready,
+ msecs_to_jiffies(100))) {
+ usbpd_err(&pd->dev, "data_role swap timed out\n");
+ mutex_unlock(&pd->swap_lock);
+ return -ETIMEDOUT;
+ }
+
+ mutex_unlock(&pd->swap_lock);
+
+ if ((*val == DUAL_ROLE_PROP_DR_HOST &&
+ pd->current_dr != DR_DFP) ||
+ (*val == DUAL_ROLE_PROP_DR_DEVICE &&
+ pd->current_dr != DR_UFP)) {
+ usbpd_err(&pd->dev, "incorrect state (%s) after data_role swap\n",
+ pd->current_dr == DR_DFP ?
+ "dfp" : "ufp");
+ return -EPROTO;
+ }
+ }
+
+ break;
+
+ case DUAL_ROLE_PROP_PR:
+ usbpd_dbg(&pd->dev, "Setting power_role to %d\n", *val);
+
+ if (*val == DUAL_ROLE_PROP_PR_SRC) {
+ if (pd->current_pr == PR_SINK)
+ do_swap = true;
+ } else if (*val == DUAL_ROLE_PROP_PR_SNK) {
+ if (pd->current_pr == PR_SRC)
+ do_swap = true;
+ } else {
+ usbpd_warn(&pd->dev, "setting power_role to 'none' unsupported\n");
+ return -ENOTSUPP;
+ }
+
+ if (do_swap) {
+ if (pd->current_state != PE_SRC_READY &&
+ pd->current_state != PE_SNK_READY) {
+ usbpd_err(&pd->dev, "power_role swap not allowed: PD not in Ready state\n");
+ return -EAGAIN;
+ }
+
+ if (pd->current_state == PE_SNK_READY &&
+ !is_sink_tx_ok(pd)) {
+ usbpd_err(&pd->dev, "Rp indicates SinkTxNG\n");
+ return -EAGAIN;
+ }
+
+ mutex_lock(&pd->swap_lock);
+ reinit_completion(&pd->is_ready);
+ pd->send_pr_swap = true;
+ kick_sm(pd, 0);
+
+ /* wait for operation to complete */
+ if (!wait_for_completion_timeout(&pd->is_ready,
+ msecs_to_jiffies(2000))) {
+ usbpd_err(&pd->dev, "power_role swap timed out\n");
+ mutex_unlock(&pd->swap_lock);
+ return -ETIMEDOUT;
+ }
+
+ mutex_unlock(&pd->swap_lock);
+
+ if ((*val == DUAL_ROLE_PROP_PR_SRC &&
+ pd->current_pr != PR_SRC) ||
+ (*val == DUAL_ROLE_PROP_PR_SNK &&
+ pd->current_pr != PR_SINK)) {
+ usbpd_err(&pd->dev, "incorrect state (%s) after power_role swap\n",
+ pd->current_pr == PR_SRC ?
+ "source" : "sink");
+ return -EPROTO;
+ }
+ }
+ break;
+
+ default:
+ usbpd_warn(&pd->dev, "unsupported property %d\n", prop);
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+static int usbpd_dr_prop_writeable(struct dual_role_phy_instance *dual_role,
+ enum dual_role_property prop)
+{
+ struct usbpd *pd = dual_role_get_drvdata(dual_role);
+
+ switch (prop) {
+ case DUAL_ROLE_PROP_MODE:
+ return 1;
+ case DUAL_ROLE_PROP_DR:
+ case DUAL_ROLE_PROP_PR:
+ if (pd)
+ return pd->current_state == PE_SNK_READY ||
+ pd->current_state == PE_SRC_READY;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int usbpd_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ int i;
+
+ add_uevent_var(env, "DATA_ROLE=%s", pd->current_dr == DR_DFP ?
+ "dfp" : "ufp");
+
+ if (pd->current_pr == PR_SINK) {
+ add_uevent_var(env, "POWER_ROLE=sink");
+ add_uevent_var(env, "SRC_CAP_ID=%d", pd->src_cap_id);
+
+ for (i = 0; i < ARRAY_SIZE(pd->received_pdos); i++)
+ add_uevent_var(env, "PDO%d=%08x", i,
+ pd->received_pdos[i]);
+
+ add_uevent_var(env, "REQUESTED_PDO=%d", pd->requested_pdo);
+ add_uevent_var(env, "SELECTED_PDO=%d", pd->selected_pdo);
+ } else {
+ add_uevent_var(env, "POWER_ROLE=source");
+ for (i = 0; i < ARRAY_SIZE(default_src_caps); i++)
+ add_uevent_var(env, "PDO%d=%08x", i,
+ default_src_caps[i]);
+ }
+
+ add_uevent_var(env, "RDO=%08x", pd->rdo);
+ add_uevent_var(env, "CONTRACT=%s", pd->in_explicit_contract ?
+ "explicit" : "implicit");
+ add_uevent_var(env, "ALT_MODE=%d", pd->vdm_state == MODE_ENTERED);
+
+ return 0;
+}
+
+static ssize_t contract_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%s\n",
+ pd->in_explicit_contract ? "explicit" : "implicit");
+}
+static DEVICE_ATTR_RO(contract);
+
+static ssize_t current_pr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ const char *pr = "none";
+
+ if (pd->current_pr == PR_SINK)
+ pr = "sink";
+ else if (pd->current_pr == PR_SRC)
+ pr = "source";
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", pr);
+}
+static DEVICE_ATTR_RO(current_pr);
+
+static ssize_t initial_pr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ const char *pr = "none";
+
+ if (pd->typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT)
+ pr = "sink";
+ else if (pd->typec_mode >= POWER_SUPPLY_TYPEC_SINK)
+ pr = "source";
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", pr);
+}
+static DEVICE_ATTR_RO(initial_pr);
+
+static ssize_t current_dr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ const char *dr = "none";
+
+ if (pd->current_dr == DR_UFP)
+ dr = "ufp";
+ else if (pd->current_dr == DR_DFP)
+ dr = "dfp";
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", dr);
+}
+static DEVICE_ATTR_RO(current_dr);
+
+static ssize_t initial_dr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ const char *dr = "none";
+
+ if (pd->typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT)
+ dr = "ufp";
+ else if (pd->typec_mode >= POWER_SUPPLY_TYPEC_SINK)
+ dr = "dfp";
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", dr);
+}
+static DEVICE_ATTR_RO(initial_dr);
+
+static ssize_t src_cap_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", pd->src_cap_id);
+}
+static DEVICE_ATTR_RO(src_cap_id);
+
+/* Dump received source PDOs in human-readable format */
+static ssize_t pdo_h_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ int i;
+ ssize_t cnt = 0;
+
+ for (i = 0; i < ARRAY_SIZE(pd->received_pdos); i++) {
+ u32 pdo = pd->received_pdos[i];
+
+ if (pdo == 0)
+ break;
+
+ cnt += scnprintf(&buf[cnt], PAGE_SIZE - cnt, "PDO %d\n", i + 1);
+
+ if (PD_SRC_PDO_TYPE(pdo) == PD_SRC_PDO_TYPE_FIXED) {
+ cnt += scnprintf(&buf[cnt], PAGE_SIZE - cnt,
+ "\tFixed supply\n"
+ "\tDual-Role Power:%d\n"
+ "\tUSB Suspend Supported:%d\n"
+ "\tExternally Powered:%d\n"
+ "\tUSB Communications Capable:%d\n"
+ "\tData Role Swap:%d\n"
+ "\tPeak Current:%d\n"
+ "\tVoltage:%d (mV)\n"
+ "\tMax Current:%d (mA)\n",
+ PD_SRC_PDO_FIXED_PR_SWAP(pdo),
+ PD_SRC_PDO_FIXED_USB_SUSP(pdo),
+ PD_SRC_PDO_FIXED_EXT_POWERED(pdo),
+ PD_SRC_PDO_FIXED_USB_COMM(pdo),
+ PD_SRC_PDO_FIXED_DR_SWAP(pdo),
+ PD_SRC_PDO_FIXED_PEAK_CURR(pdo),
+ PD_SRC_PDO_FIXED_VOLTAGE(pdo) * 50,
+ PD_SRC_PDO_FIXED_MAX_CURR(pdo) * 10);
+ } else if (PD_SRC_PDO_TYPE(pdo) == PD_SRC_PDO_TYPE_BATTERY) {
+ cnt += scnprintf(&buf[cnt], PAGE_SIZE - cnt,
+ "\tBattery supply\n"
+ "\tMax Voltage:%d (mV)\n"
+ "\tMin Voltage:%d (mV)\n"
+ "\tMax Power:%d (mW)\n",
+ PD_SRC_PDO_VAR_BATT_MAX_VOLT(pdo) * 50,
+ PD_SRC_PDO_VAR_BATT_MIN_VOLT(pdo) * 50,
+ PD_SRC_PDO_VAR_BATT_MAX(pdo) * 250);
+ } else if (PD_SRC_PDO_TYPE(pdo) == PD_SRC_PDO_TYPE_VARIABLE) {
+ cnt += scnprintf(&buf[cnt], PAGE_SIZE - cnt,
+ "\tVariable supply\n"
+ "\tMax Voltage:%d (mV)\n"
+ "\tMin Voltage:%d (mV)\n"
+ "\tMax Current:%d (mA)\n",
+ PD_SRC_PDO_VAR_BATT_MAX_VOLT(pdo) * 50,
+ PD_SRC_PDO_VAR_BATT_MIN_VOLT(pdo) * 50,
+ PD_SRC_PDO_VAR_BATT_MAX(pdo) * 10);
+ } else if (PD_SRC_PDO_TYPE(pdo) == PD_SRC_PDO_TYPE_AUGMENTED) {
+ cnt += scnprintf(&buf[cnt], PAGE_SIZE - cnt,
+ "\tProgrammable Power supply\n"
+ "\tMax Voltage:%d (mV)\n"
+ "\tMin Voltage:%d (mV)\n"
+ "\tMax Current:%d (mA)\n",
+ PD_APDO_MAX_VOLT(pdo) * 100,
+ PD_APDO_MIN_VOLT(pdo) * 100,
+ PD_APDO_MAX_CURR(pdo) * 50);
+ } else {
+ cnt += scnprintf(&buf[cnt], PAGE_SIZE - cnt,
+ "Invalid PDO\n");
+ }
+
+ buf[cnt++] = '\n';
+ }
+
+ return cnt;
+}
+static DEVICE_ATTR_RO(pdo_h);
+
+static ssize_t pdo_n_show(struct device *dev, struct device_attribute *attr,
+ char *buf);
+
+#define PDO_ATTR(n) { \
+ .attr = { .name = __stringify(pdo##n), .mode = S_IRUGO }, \
+ .show = pdo_n_show, \
+}
+static struct device_attribute dev_attr_pdos[] = {
+ PDO_ATTR(1),
+ PDO_ATTR(2),
+ PDO_ATTR(3),
+ PDO_ATTR(4),
+ PDO_ATTR(5),
+ PDO_ATTR(6),
+ PDO_ATTR(7),
+};
+
+static ssize_t pdo_n_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dev_attr_pdos); i++)
+ if (attr == &dev_attr_pdos[i])
+ /* dump the PDO as a hex string */
+ return snprintf(buf, PAGE_SIZE, "%08x\n",
+ pd->received_pdos[i]);
+
+ usbpd_err(&pd->dev, "Invalid PDO index\n");
+ return -EINVAL;
+}
+
+static ssize_t select_pdo_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ int src_cap_id;
+ int pdo, uv = 0, ua = 0;
+ int ret;
+
+ mutex_lock(&pd->swap_lock);
+
+ /* Only allowed if we are already in explicit sink contract */
+ if (pd->current_state != PE_SNK_READY || !is_sink_tx_ok(pd)) {
+ usbpd_err(&pd->dev, "select_pdo: Cannot select new PDO yet\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = sscanf(buf, "%d %d %d %d", &src_cap_id, &pdo, &uv, &ua);
+ if (ret != 2 && ret != 4) {
+ usbpd_err(&pd->dev, "select_pdo: Must specify <src cap id> <PDO> [<uV> <uA>]\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (src_cap_id != pd->src_cap_id) {
+ usbpd_err(&pd->dev, "select_pdo: src_cap_id mismatch. Requested:%d, current:%d\n",
+ src_cap_id, pd->src_cap_id);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (pdo < 1 || pdo > 7) {
+ usbpd_err(&pd->dev, "select_pdo: invalid PDO:%d\n", pdo);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = pd_select_pdo(pd, pdo, uv, ua);
+ if (ret)
+ goto out;
+
+ reinit_completion(&pd->is_ready);
+ pd->send_request = true;
+ kick_sm(pd, 0);
+
+ /* wait for operation to complete */
+ if (!wait_for_completion_timeout(&pd->is_ready,
+ msecs_to_jiffies(1000))) {
+ usbpd_err(&pd->dev, "select_pdo: request timed out\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ /* determine if request was accepted/rejected */
+ if (pd->selected_pdo != pd->requested_pdo ||
+ pd->current_voltage != pd->requested_voltage) {
+ usbpd_err(&pd->dev, "select_pdo: request rejected\n");
+ ret = -EINVAL;
+ }
+
+out:
+ pd->send_request = false;
+ mutex_unlock(&pd->swap_lock);
+ return ret ? ret : size;
+}
+
+static ssize_t select_pdo_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", pd->selected_pdo);
+}
+static DEVICE_ATTR_RW(select_pdo);
+
+static ssize_t rdo_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+
+ /* dump the RDO as a hex string */
+ return snprintf(buf, PAGE_SIZE, "%08x\n", pd->rdo);
+}
+static DEVICE_ATTR_RO(rdo);
+
+static ssize_t rdo_h_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ int pos = PD_RDO_OBJ_POS(pd->rdo);
+ int type = PD_SRC_PDO_TYPE(pd->received_pdos[pos]);
+ int len;
+
+ len = scnprintf(buf, PAGE_SIZE, "Request Data Object\n"
+ "\tObj Pos:%d\n"
+ "\tGiveback:%d\n"
+ "\tCapability Mismatch:%d\n"
+ "\tUSB Communications Capable:%d\n"
+ "\tNo USB Suspend:%d\n",
+ PD_RDO_OBJ_POS(pd->rdo),
+ PD_RDO_GIVEBACK(pd->rdo),
+ PD_RDO_MISMATCH(pd->rdo),
+ PD_RDO_USB_COMM(pd->rdo),
+ PD_RDO_NO_USB_SUSP(pd->rdo));
+
+ switch (type) {
+ case PD_SRC_PDO_TYPE_FIXED:
+ case PD_SRC_PDO_TYPE_VARIABLE:
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ "(Fixed/Variable)\n"
+ "\tOperating Current:%d (mA)\n"
+ "\t%s Current:%d (mA)\n",
+ PD_RDO_FIXED_CURR(pd->rdo) * 10,
+ PD_RDO_GIVEBACK(pd->rdo) ? "Min" : "Max",
+ PD_RDO_FIXED_CURR_MINMAX(pd->rdo) * 10);
+ break;
+
+ case PD_SRC_PDO_TYPE_BATTERY:
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ "(Battery)\n"
+ "\tOperating Power:%d (mW)\n"
+ "\t%s Power:%d (mW)\n",
+ PD_RDO_FIXED_CURR(pd->rdo) * 250,
+ PD_RDO_GIVEBACK(pd->rdo) ? "Min" : "Max",
+ PD_RDO_FIXED_CURR_MINMAX(pd->rdo) * 250);
+ break;
+
+ case PD_SRC_PDO_TYPE_AUGMENTED:
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ "(Programmable)\n"
+ "\tOutput Voltage:%d (mV)\n"
+ "\tOperating Current:%d (mA)\n",
+ PD_RDO_PROG_VOLTAGE(pd->rdo) * 20,
+ PD_RDO_PROG_CURR(pd->rdo) * 50);
+ break;
+ }
+
+ return len;
+}
+static DEVICE_ATTR_RO(rdo_h);
+
+static ssize_t hard_reset_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct usbpd *pd = dev_get_drvdata(dev);
+ int val = 0;
+
+ if (sscanf(buf, "%d\n", &val) != 1)
+ return -EINVAL;
+
+ if (val)
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ PE_SRC_HARD_RESET : PE_SNK_HARD_RESET);
+
+ return size;
+}
+static DEVICE_ATTR_WO(hard_reset);
+
+static struct attribute *usbpd_attrs[] = {
+ &dev_attr_contract.attr,
+ &dev_attr_initial_pr.attr,
+ &dev_attr_current_pr.attr,
+ &dev_attr_initial_dr.attr,
+ &dev_attr_current_dr.attr,
+ &dev_attr_src_cap_id.attr,
+ &dev_attr_pdo_h.attr,
+ &dev_attr_pdos[0].attr,
+ &dev_attr_pdos[1].attr,
+ &dev_attr_pdos[2].attr,
+ &dev_attr_pdos[3].attr,
+ &dev_attr_pdos[4].attr,
+ &dev_attr_pdos[5].attr,
+ &dev_attr_pdos[6].attr,
+ &dev_attr_select_pdo.attr,
+ &dev_attr_rdo.attr,
+ &dev_attr_rdo_h.attr,
+ &dev_attr_hard_reset.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(usbpd);
+
+static struct class usbpd_class = {
+ .name = "usbpd",
+ .owner = THIS_MODULE,
+ .dev_uevent = usbpd_uevent,
+ .dev_groups = usbpd_groups,
+};
+
+static int match_usbpd_device(struct device *dev, const void *data)
+{
+ return dev->parent == data;
+}
+
+static void devm_usbpd_put(struct device *dev, void *res)
+{
+ struct usbpd **ppd = res;
+
+ put_device(&(*ppd)->dev);
+}
+
+struct usbpd *devm_usbpd_get_by_phandle(struct device *dev, const char *phandle)
+{
+ struct usbpd **ptr, *pd = NULL;
+ struct device_node *pd_np;
+ struct platform_device *pdev;
+ struct device *pd_dev;
+
+ if (!usbpd_class.p) /* usbpd_init() not yet called */
+ return ERR_PTR(-EAGAIN);
+
+ if (!dev->of_node)
+ return ERR_PTR(-EINVAL);
+
+ pd_np = of_parse_phandle(dev->of_node, phandle, 0);
+ if (!pd_np)
+ return ERR_PTR(-ENXIO);
+
+ pdev = of_find_device_by_node(pd_np);
+ if (!pdev)
+ return ERR_PTR(-ENODEV);
+
+ pd_dev = class_find_device(&usbpd_class, NULL, &pdev->dev,
+ match_usbpd_device);
+ if (!pd_dev) {
+ platform_device_put(pdev);
+ /* device was found but maybe hadn't probed yet, so defer */
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ ptr = devres_alloc(devm_usbpd_put, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr) {
+ put_device(pd_dev);
+ platform_device_put(pdev);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ pd = dev_get_drvdata(pd_dev);
+ if (!pd)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ *ptr = pd;
+ devres_add(dev, ptr);
+
+ return pd;
+}
+EXPORT_SYMBOL(devm_usbpd_get_by_phandle);
+
+static int num_pd_instances;
+
+/**
+ * usbpd_create - Create a new instance of USB PD protocol/policy engine
+ * @parent - parent device to associate with
+ *
+ * This creates a new usbpd class device which manages the state of a
+ * USB PD-capable port. The parent device that is passed in should be
+ * associated with the physical device port, e.g. a PD PHY.
+ *
+ * Return: struct usbpd pointer, or an ERR_PTR value
+ */
+struct usbpd *usbpd_create(struct device *parent)
+{
+ int ret;
+ struct usbpd *pd;
+
+ pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+ if (!pd)
+ return ERR_PTR(-ENOMEM);
+
+ device_initialize(&pd->dev);
+ pd->dev.class = &usbpd_class;
+ pd->dev.parent = parent;
+ dev_set_drvdata(&pd->dev, pd);
+
+ ret = dev_set_name(&pd->dev, "usbpd%d", num_pd_instances++);
+ if (ret)
+ goto free_pd;
+
+ ret = device_init_wakeup(&pd->dev, true);
+ if (ret)
+ goto free_pd;
+
+ ret = device_add(&pd->dev);
+ if (ret)
+ goto free_pd;
+
+ pd->wq = alloc_ordered_workqueue("usbpd_wq", WQ_FREEZABLE | WQ_HIGHPRI);
+ if (!pd->wq) {
+ ret = -ENOMEM;
+ goto del_pd;
+ }
+ INIT_WORK(&pd->sm_work, usbpd_sm);
+ hrtimer_init(&pd->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ pd->timer.function = pd_timeout;
+ mutex_init(&pd->swap_lock);
+
+ pd->usb_psy = power_supply_get_by_name("usb");
+ if (!pd->usb_psy) {
+ usbpd_dbg(&pd->dev, "Could not get USB power_supply, deferring probe\n");
+ ret = -EPROBE_DEFER;
+ goto destroy_wq;
+ }
+
+ /*
+ * associate extcon with the parent dev as it could have a DT
+ * node which will be useful for extcon_get_edev_by_phandle()
+ */
+ pd->extcon = devm_extcon_dev_allocate(parent, usbpd_extcon_cable);
+ if (IS_ERR(pd->extcon)) {
+ usbpd_err(&pd->dev, "failed to allocate extcon device\n");
+ ret = PTR_ERR(pd->extcon);
+ goto put_psy;
+ }
+
+ pd->extcon->mutually_exclusive = usbpd_extcon_exclusive;
+ ret = devm_extcon_dev_register(parent, pd->extcon);
+ if (ret) {
+ usbpd_err(&pd->dev, "failed to register extcon device\n");
+ goto put_psy;
+ }
+
+ pd->vbus = devm_regulator_get(parent, "vbus");
+ if (IS_ERR(pd->vbus)) {
+ ret = PTR_ERR(pd->vbus);
+ goto put_psy;
+ }
+
+ pd->vconn = devm_regulator_get(parent, "vconn");
+ if (IS_ERR(pd->vconn)) {
+ ret = PTR_ERR(pd->vconn);
+ goto put_psy;
+ }
+
+ pd->vconn_is_external = device_property_present(parent,
+ "qcom,vconn-uses-external-source");
+
+ pd->num_sink_caps = device_property_read_u32_array(parent,
+ "qcom,default-sink-caps", NULL, 0);
+ if (pd->num_sink_caps > 0) {
+ int i;
+ u32 sink_caps[14];
+
+ if (pd->num_sink_caps % 2 || pd->num_sink_caps > 14) {
+ ret = -EINVAL;
+ usbpd_err(&pd->dev, "default-sink-caps must be be specified as voltage/current, max 7 pairs\n");
+ goto put_psy;
+ }
+
+ ret = device_property_read_u32_array(parent,
+ "qcom,default-sink-caps", sink_caps,
+ pd->num_sink_caps);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error reading default-sink-caps\n");
+ goto put_psy;
+ }
+
+ pd->num_sink_caps /= 2;
+
+ for (i = 0; i < pd->num_sink_caps; i++) {
+ int v = sink_caps[i * 2] / 50;
+ int c = sink_caps[i * 2 + 1] / 10;
+
+ pd->sink_caps[i] =
+ PD_SNK_PDO_FIXED(0, 0, 0, 0, 0, v, c);
+ }
+
+ /* First PDO includes additional capabilities */
+ pd->sink_caps[0] |= PD_SNK_PDO_FIXED(1, 0, 0, 1, 1, 0, 0);
+ } else {
+ memcpy(pd->sink_caps, default_snk_caps,
+ sizeof(default_snk_caps));
+ pd->num_sink_caps = ARRAY_SIZE(default_snk_caps);
+ }
+
+ /*
+ * Register the Android dual-role class (/sys/class/dual_role_usb/).
+ * The first instance should be named "otg_default" as that's what
+ * Android expects.
+ * Note this is different than the /sys/class/usbpd/ created above.
+ */
+ pd->dr_desc.name = (num_pd_instances == 1) ?
+ "otg_default" : dev_name(&pd->dev);
+ pd->dr_desc.supported_modes = DUAL_ROLE_SUPPORTED_MODES_DFP_AND_UFP;
+ pd->dr_desc.properties = usbpd_dr_properties;
+ pd->dr_desc.num_properties = ARRAY_SIZE(usbpd_dr_properties);
+ pd->dr_desc.get_property = usbpd_dr_get_property;
+ pd->dr_desc.set_property = usbpd_dr_set_property;
+ pd->dr_desc.property_is_writeable = usbpd_dr_prop_writeable;
+
+ pd->dual_role = devm_dual_role_instance_register(&pd->dev,
+ &pd->dr_desc);
+ if (IS_ERR(pd->dual_role)) {
+ usbpd_err(&pd->dev, "could not register dual_role instance\n");
+ goto put_psy;
+ } else {
+ pd->dual_role->drv_data = pd;
+ }
+
+ /* default support as PD 2.0 source or sink */
+ pd->spec_rev = USBPD_REV_20;
+ pd->current_pr = PR_NONE;
+ pd->current_dr = DR_NONE;
+ list_add_tail(&pd->instance, &_usbpd);
+
+ spin_lock_init(&pd->rx_lock);
+ INIT_LIST_HEAD(&pd->rx_q);
+ INIT_LIST_HEAD(&pd->svid_handlers);
+ init_completion(&pd->is_ready);
+
+ pd->psy_nb.notifier_call = psy_changed;
+ ret = power_supply_reg_notifier(&pd->psy_nb);
+ if (ret)
+ goto del_inst;
+
+ /* force read initial power_supply values */
+ psy_changed(&pd->psy_nb, PSY_EVENT_PROP_CHANGED, pd->usb_psy);
+
+ return pd;
+
+del_inst:
+ list_del(&pd->instance);
+put_psy:
+ power_supply_put(pd->usb_psy);
+destroy_wq:
+ destroy_workqueue(pd->wq);
+del_pd:
+ device_del(&pd->dev);
+free_pd:
+ num_pd_instances--;
+ kfree(pd);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(usbpd_create);
+
+/**
+ * usbpd_destroy - Removes and frees a usbpd instance
+ * @pd: the instance to destroy
+ */
+void usbpd_destroy(struct usbpd *pd)
+{
+ if (!pd)
+ return;
+
+ list_del(&pd->instance);
+ power_supply_unreg_notifier(&pd->psy_nb);
+ power_supply_put(pd->usb_psy);
+ destroy_workqueue(pd->wq);
+ device_del(&pd->dev);
+ kfree(pd);
+}
+EXPORT_SYMBOL(usbpd_destroy);
+
+static int __init usbpd_init(void)
+{
+ usbpd_ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "usb_pd", 0);
+ return class_register(&usbpd_class);
+}
+module_init(usbpd_init);
+
+static void __exit usbpd_exit(void)
+{
+ class_unregister(&usbpd_class);
+}
+module_exit(usbpd_exit);
+
+MODULE_DESCRIPTION("USB Power Delivery Policy Engine");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/pd/qpnp-pdphy.c b/drivers/usb/pd/qpnp-pdphy.c
new file mode 100644
index 000000000000..b454442e2471
--- /dev/null
+++ b/drivers/usb/pd/qpnp-pdphy.c
@@ -0,0 +1,917 @@
+/* Copyright (c) 2016-2017, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/irq.h>
+#include <linux/of_irq.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include "usbpd.h"
+
+#define USB_PDPHY_MAX_DATA_OBJ_LEN 28
+#define USB_PDPHY_MSG_HDR_LEN 2
+
+/* PD PHY register offsets and bit fields */
+#define USB_PDPHY_MSG_CONFIG 0x40
+#define MSG_CONFIG_PORT_DATA_ROLE BIT(3)
+#define MSG_CONFIG_PORT_POWER_ROLE BIT(2)
+#define MSG_CONFIG_SPEC_REV_MASK (BIT(1) | BIT(0))
+
+#define USB_PDPHY_EN_CONTROL 0x46
+#define CONTROL_ENABLE BIT(0)
+
+#define USB_PDPHY_RX_STATUS 0x4A
+#define RX_FRAME_TYPE (BIT(0) | BIT(1) | BIT(2))
+
+#define USB_PDPHY_FRAME_FILTER 0x4C
+#define FRAME_FILTER_EN_HARD_RESET BIT(5)
+#define FRAME_FILTER_EN_SOP BIT(0)
+
+#define USB_PDPHY_TX_SIZE 0x42
+#define TX_SIZE_MASK 0xF
+
+#define USB_PDPHY_TX_CONTROL 0x44
+#define TX_CONTROL_RETRY_COUNT (BIT(6) | BIT(5))
+#define TX_CONTROL_FRAME_TYPE (BIT(4) | BIT(3) | BIT(2))
+#define TX_CONTROL_FRAME_TYPE_CABLE_RESET (0x1 << 2)
+#define TX_CONTROL_SEND_SIGNAL BIT(1)
+#define TX_CONTROL_SEND_MSG BIT(0)
+
+#define USB_PDPHY_RX_SIZE 0x48
+
+#define USB_PDPHY_RX_ACKNOWLEDGE 0x4B
+#define RX_BUFFER_TOKEN BIT(0)
+
+#define USB_PDPHY_BIST_MODE 0x4E
+#define BIST_MODE_MASK 0xF
+#define BIST_ENABLE BIT(7)
+#define PD_MSG_BIST 0x3
+#define PD_BIST_TEST_DATA_MODE 0x8
+
+#define USB_PDPHY_TX_BUFFER_HDR 0x60
+#define USB_PDPHY_TX_BUFFER_DATA 0x62
+
+#define USB_PDPHY_RX_BUFFER 0x80
+
+#define USB_PDPHY_SEC_ACCESS 0xD0
+#define USB_PDPHY_TRIM_3 0xF3
+
+/* VDD regulator */
+#define VDD_PDPHY_VOL_MIN 2800000 /* uV */
+#define VDD_PDPHY_VOL_MAX 3300000 /* uV */
+#define VDD_PDPHY_HPM_LOAD 3000 /* uA */
+
+/* timers */
+#define RECEIVER_RESPONSE_TIME 15 /* tReceiverResponse */
+#define HARD_RESET_COMPLETE_TIME 5 /* tHardResetComplete */
+
+struct usb_pdphy {
+ struct device *dev;
+ struct regmap *regmap;
+
+ u16 base;
+ struct regulator *vdd_pdphy;
+
+ /* irqs */
+ int sig_tx_irq;
+ int sig_rx_irq;
+ int msg_tx_irq;
+ int msg_rx_irq;
+ int msg_tx_failed_irq;
+ int msg_tx_discarded_irq;
+ int msg_rx_discarded_irq;
+
+ void (*signal_cb)(struct usbpd *pd, enum pd_sig_type sig);
+ void (*msg_rx_cb)(struct usbpd *pd, enum pd_sop_type sop,
+ u8 *buf, size_t len);
+ void (*shutdown_cb)(struct usbpd *pd);
+
+ /* write waitq */
+ wait_queue_head_t tx_waitq;
+
+ bool is_opened;
+ int tx_status;
+ u8 frame_filter_val;
+ bool in_test_data_mode;
+ bool rx_busy;
+
+ enum data_role data_role;
+ enum power_role power_role;
+
+ struct usbpd *usbpd;
+
+ /* debug */
+ struct dentry *debug_root;
+ unsigned int tx_bytes; /* hdr + data */
+ unsigned int rx_bytes; /* hdr + data */
+ unsigned int sig_tx_cnt;
+ unsigned int sig_rx_cnt;
+ unsigned int msg_tx_cnt;
+ unsigned int msg_rx_cnt;
+ unsigned int msg_tx_failed_cnt;
+ unsigned int msg_tx_discarded_cnt;
+ unsigned int msg_rx_discarded_cnt;
+};
+
+static struct usb_pdphy *__pdphy;
+
+static int pdphy_dbg_status(struct seq_file *s, void *p)
+{
+ struct usb_pdphy *pdphy = s->private;
+
+ seq_printf(s,
+ "PD Phy driver status\n"
+ "==================================================\n");
+ seq_printf(s, "opened: %10d\n", pdphy->is_opened);
+ seq_printf(s, "tx status: %10d\n", pdphy->tx_status);
+ seq_printf(s, "tx bytes: %10u\n", pdphy->tx_bytes);
+ seq_printf(s, "rx bytes: %10u\n", pdphy->rx_bytes);
+ seq_printf(s, "data role: %10u\n", pdphy->data_role);
+ seq_printf(s, "power role: %10u\n", pdphy->power_role);
+ seq_printf(s, "frame filter: %10u\n", pdphy->frame_filter_val);
+ seq_printf(s, "sig tx cnt: %10u\n", pdphy->sig_tx_cnt);
+ seq_printf(s, "sig rx cnt: %10u\n", pdphy->sig_rx_cnt);
+ seq_printf(s, "msg tx cnt: %10u\n", pdphy->msg_tx_cnt);
+ seq_printf(s, "msg rx cnt: %10u\n", pdphy->msg_rx_cnt);
+ seq_printf(s, "msg tx failed cnt: %10u\n",
+ pdphy->msg_tx_failed_cnt);
+ seq_printf(s, "msg tx discarded cnt: %10u\n",
+ pdphy->msg_tx_discarded_cnt);
+ seq_printf(s, "msg rx discarded cnt: %10u\n",
+ pdphy->msg_rx_discarded_cnt);
+
+ return 0;
+}
+
+static int pdphy_dbg_status_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pdphy_dbg_status, inode->i_private);
+}
+
+static const struct file_operations status_ops = {
+ .owner = THIS_MODULE,
+ .open = pdphy_dbg_status_open,
+ .llseek = seq_lseek,
+ .read = seq_read,
+ .release = single_release,
+};
+
+static void pdphy_create_debugfs_entries(struct usb_pdphy *pdphy)
+{
+ struct dentry *ent;
+
+ pdphy->debug_root = debugfs_create_dir("usb-pdphy", NULL);
+ if (!pdphy->debug_root) {
+ dev_warn(pdphy->dev, "Couldn't create debug dir\n");
+ return;
+ }
+
+ ent = debugfs_create_file("status", S_IRUSR, pdphy->debug_root, pdphy,
+ &status_ops);
+ if (!ent) {
+ dev_warn(pdphy->dev, "Couldn't create status file\n");
+ debugfs_remove(pdphy->debug_root);
+ }
+}
+
+static int pdphy_enable_power(struct usb_pdphy *pdphy, bool on)
+{
+ int ret = 0;
+
+ dev_dbg(pdphy->dev, "%s turn %s regulator.\n", __func__,
+ on ? "on" : "off");
+
+ if (!on)
+ goto disable_pdphy_vdd;
+
+ ret = regulator_set_load(pdphy->vdd_pdphy, VDD_PDPHY_HPM_LOAD);
+ if (ret < 0) {
+ dev_err(pdphy->dev, "Unable to set HPM of vdd_pdphy:%d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_set_voltage(pdphy->vdd_pdphy, VDD_PDPHY_VOL_MIN,
+ VDD_PDPHY_VOL_MAX);
+ if (ret) {
+ dev_err(pdphy->dev,
+ "set voltage failed for vdd_pdphy:%d\n", ret);
+ goto put_pdphy_vdd_lpm;
+ }
+
+ ret = regulator_enable(pdphy->vdd_pdphy);
+ if (ret) {
+ dev_err(pdphy->dev, "Unable to enable vdd_pdphy:%d\n", ret);
+ goto unset_pdphy_vdd;
+ }
+
+ dev_dbg(pdphy->dev, "%s: PD PHY regulator turned ON.\n", __func__);
+ return ret;
+
+disable_pdphy_vdd:
+ ret = regulator_disable(pdphy->vdd_pdphy);
+ if (ret)
+ dev_err(pdphy->dev, "Unable to disable vdd_pdphy:%d\n", ret);
+
+unset_pdphy_vdd:
+ ret = regulator_set_voltage(pdphy->vdd_pdphy, 0, VDD_PDPHY_VOL_MAX);
+ if (ret)
+ dev_err(pdphy->dev,
+ "Unable to set (0) voltage for vdd_pdphy:%d\n", ret);
+
+put_pdphy_vdd_lpm:
+ ret = regulator_set_load(pdphy->vdd_pdphy, 0);
+ if (ret < 0)
+ dev_err(pdphy->dev, "Unable to set (0) HPM of vdd_pdphy\n");
+
+ return ret;
+}
+
+void pdphy_enable_irq(struct usb_pdphy *pdphy, bool enable)
+{
+ if (enable) {
+ enable_irq(pdphy->sig_tx_irq);
+ enable_irq(pdphy->sig_rx_irq);
+ enable_irq_wake(pdphy->sig_rx_irq);
+ enable_irq(pdphy->msg_tx_irq);
+ if (!pdphy->in_test_data_mode) {
+ enable_irq(pdphy->msg_rx_irq);
+ enable_irq_wake(pdphy->msg_rx_irq);
+ }
+ enable_irq(pdphy->msg_tx_failed_irq);
+ enable_irq(pdphy->msg_tx_discarded_irq);
+ enable_irq(pdphy->msg_rx_discarded_irq);
+ return;
+ }
+
+ disable_irq(pdphy->sig_tx_irq);
+ disable_irq(pdphy->sig_rx_irq);
+ disable_irq_wake(pdphy->sig_rx_irq);
+ disable_irq(pdphy->msg_tx_irq);
+ if (!pdphy->in_test_data_mode) {
+ disable_irq(pdphy->msg_rx_irq);
+ disable_irq_wake(pdphy->msg_rx_irq);
+ }
+ disable_irq(pdphy->msg_tx_failed_irq);
+ disable_irq(pdphy->msg_tx_discarded_irq);
+ disable_irq(pdphy->msg_rx_discarded_irq);
+}
+
+static int pdphy_reg_read(struct usb_pdphy *pdphy, u8 *val, u16 addr, int count)
+{
+ int ret;
+
+ ret = regmap_bulk_read(pdphy->regmap, pdphy->base + addr, val, count);
+ if (ret) {
+ dev_err(pdphy->dev, "read failed: addr=0x%04x, ret=%d\n",
+ pdphy->base + addr, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Write multiple registers to device with block of data */
+static int pdphy_bulk_reg_write(struct usb_pdphy *pdphy, u16 addr,
+ const void *val, u8 val_cnt)
+{
+ int ret;
+
+ ret = regmap_bulk_write(pdphy->regmap, pdphy->base + addr,
+ val, val_cnt);
+ if (ret) {
+ dev_err(pdphy->dev, "bulk write failed: addr=0x%04x, ret=%d\n",
+ pdphy->base + addr, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Writes a single byte to the specified register */
+static inline int pdphy_reg_write(struct usb_pdphy *pdphy, u16 addr, u8 val)
+{
+ return pdphy_bulk_reg_write(pdphy, addr, &val, 1);
+}
+
+/* Writes to the specified register limited by the bit mask */
+static int pdphy_masked_write(struct usb_pdphy *pdphy, u16 addr,
+ u8 mask, u8 val)
+{
+ int ret;
+
+ ret = regmap_update_bits(pdphy->regmap, pdphy->base + addr, mask, val);
+ if (ret) {
+ dev_err(pdphy->dev, "write failed: addr=0x%04x, ret=%d\n",
+ pdphy->base + addr, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int pd_phy_update_roles(enum data_role dr, enum power_role pr)
+{
+ struct usb_pdphy *pdphy = __pdphy;
+
+ return pdphy_masked_write(pdphy, USB_PDPHY_MSG_CONFIG,
+ (MSG_CONFIG_PORT_DATA_ROLE | MSG_CONFIG_PORT_POWER_ROLE),
+ ((dr == DR_DFP ? MSG_CONFIG_PORT_DATA_ROLE : 0) |
+ (pr == PR_SRC ? MSG_CONFIG_PORT_POWER_ROLE : 0)));
+}
+EXPORT_SYMBOL(pd_phy_update_roles);
+
+int pd_phy_open(struct pd_phy_params *params)
+{
+ int ret;
+ struct usb_pdphy *pdphy = __pdphy;
+
+ if (!pdphy) {
+ pr_err("%s: pdphy not found\n", __func__);
+ return -ENODEV;
+ }
+
+ if (pdphy->is_opened) {
+ dev_err(pdphy->dev, "%s: already opened\n", __func__);
+ return -EBUSY;
+ }
+
+ pdphy->signal_cb = params->signal_cb;
+ pdphy->msg_rx_cb = params->msg_rx_cb;
+ pdphy->shutdown_cb = params->shutdown_cb;
+ pdphy->data_role = params->data_role;
+ pdphy->power_role = params->power_role;
+ pdphy->frame_filter_val = params->frame_filter_val;
+
+ dev_dbg(pdphy->dev, "%s: DR %x PR %x frame filter val %x\n", __func__,
+ pdphy->data_role, pdphy->power_role, pdphy->frame_filter_val);
+
+ ret = pdphy_enable_power(pdphy, true);
+ if (ret)
+ return ret;
+
+ /* update data and power role to be used in GoodCRC generation */
+ ret = pd_phy_update_roles(pdphy->data_role, pdphy->power_role);
+ if (ret)
+ return ret;
+
+ /* PD 2.0 phy */
+ ret = pdphy_masked_write(pdphy, USB_PDPHY_MSG_CONFIG,
+ MSG_CONFIG_SPEC_REV_MASK, USBPD_REV_20);
+ if (ret)
+ return ret;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_EN_CONTROL, 0);
+ if (ret)
+ return ret;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_EN_CONTROL, CONTROL_ENABLE);
+ if (ret)
+ return ret;
+
+ /* update frame filter */
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_FRAME_FILTER,
+ pdphy->frame_filter_val);
+ if (ret)
+ return ret;
+
+ /* initialize Rx buffer ownership to PDPHY HW */
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_RX_ACKNOWLEDGE, 0);
+ if (ret)
+ return ret;
+
+ pdphy->is_opened = true;
+ pdphy_enable_irq(pdphy, true);
+
+ return ret;
+}
+EXPORT_SYMBOL(pd_phy_open);
+
+int pd_phy_signal(enum pd_sig_type sig)
+{
+ u8 val;
+ int ret;
+ struct usb_pdphy *pdphy = __pdphy;
+
+ dev_dbg(pdphy->dev, "%s: type %d\n", __func__, sig);
+
+ if (!pdphy) {
+ pr_err("%s: pdphy not found\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!pdphy->is_opened) {
+ dev_dbg(pdphy->dev, "%s: pdphy disabled\n", __func__);
+ return -ENODEV;
+ }
+
+ pdphy->tx_status = -EINPROGRESS;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, 0);
+ if (ret)
+ return ret;
+
+ usleep_range(2, 3);
+
+ val = (sig == CABLE_RESET_SIG ? TX_CONTROL_FRAME_TYPE_CABLE_RESET : 0)
+ | TX_CONTROL_SEND_SIGNAL;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, val);
+ if (ret)
+ return ret;
+
+ ret = wait_event_interruptible_timeout(pdphy->tx_waitq,
+ pdphy->tx_status != -EINPROGRESS,
+ msecs_to_jiffies(HARD_RESET_COMPLETE_TIME));
+ if (ret <= 0) {
+ dev_err(pdphy->dev, "%s: failed ret %d", __func__, ret);
+ return ret ? ret : -ETIMEDOUT;
+ }
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, 0);
+
+ if (pdphy->tx_status)
+ return pdphy->tx_status;
+
+ if (sig == HARD_RESET_SIG)
+ /* Frame filter is reconfigured in pd_phy_open() */
+ return pdphy_reg_write(pdphy, USB_PDPHY_FRAME_FILTER, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL(pd_phy_signal);
+
+int pd_phy_write(u16 hdr, const u8 *data, size_t data_len, enum pd_sop_type sop)
+{
+ u8 val;
+ int ret;
+ size_t total_len = data_len + USB_PDPHY_MSG_HDR_LEN;
+ struct usb_pdphy *pdphy = __pdphy;
+
+ dev_dbg(pdphy->dev, "%s: hdr %x frame sop_type %d\n",
+ __func__, hdr, sop);
+
+ if (data && data_len)
+ print_hex_dump_debug("tx data obj:", DUMP_PREFIX_NONE, 32, 4,
+ data, data_len, false);
+
+ if (!pdphy) {
+ pr_err("%s: pdphy not found\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!pdphy->is_opened) {
+ dev_dbg(pdphy->dev, "%s: pdphy disabled\n", __func__);
+ return -ENODEV;
+ }
+
+ if (data_len > USB_PDPHY_MAX_DATA_OBJ_LEN) {
+ dev_err(pdphy->dev, "%s: invalid data object len %zu\n",
+ __func__, data_len);
+ return -EINVAL;
+ }
+
+ ret = pdphy_reg_read(pdphy, &val, USB_PDPHY_RX_ACKNOWLEDGE, 1);
+ if (ret || val || pdphy->rx_busy) {
+ dev_err(pdphy->dev, "%s: RX message pending\n", __func__);
+ return -EBUSY;
+ }
+
+ pdphy->tx_status = -EINPROGRESS;
+
+ /* write 2 byte SOP message header */
+ ret = pdphy_bulk_reg_write(pdphy, USB_PDPHY_TX_BUFFER_HDR, (u8 *)&hdr,
+ USB_PDPHY_MSG_HDR_LEN);
+ if (ret)
+ return ret;
+
+ if (data && data_len) {
+ /* write data objects of SOP message */
+ ret = pdphy_bulk_reg_write(pdphy, USB_PDPHY_TX_BUFFER_DATA,
+ data, data_len);
+ if (ret)
+ return ret;
+ }
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_SIZE, total_len - 1);
+ if (ret)
+ return ret;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, 0);
+ if (ret)
+ return ret;
+
+ usleep_range(2, 3);
+
+ val = TX_CONTROL_RETRY_COUNT | (sop << 2) | TX_CONTROL_SEND_MSG;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, val);
+ if (ret)
+ return ret;
+
+ ret = wait_event_interruptible_timeout(pdphy->tx_waitq,
+ pdphy->tx_status != -EINPROGRESS,
+ msecs_to_jiffies(RECEIVER_RESPONSE_TIME));
+ if (ret <= 0) {
+ dev_err(pdphy->dev, "%s: failed ret %d", __func__, ret);
+ return ret ? ret : -ETIMEDOUT;
+ }
+
+ if (hdr && !pdphy->tx_status)
+ pdphy->tx_bytes += data_len + USB_PDPHY_MSG_HDR_LEN;
+
+ return pdphy->tx_status ? pdphy->tx_status : 0;
+}
+EXPORT_SYMBOL(pd_phy_write);
+
+void pd_phy_close(void)
+{
+ int ret;
+ struct usb_pdphy *pdphy = __pdphy;
+
+ if (!pdphy) {
+ pr_err("%s: pdphy not found\n", __func__);
+ return;
+ }
+
+ if (!pdphy->is_opened) {
+ dev_err(pdphy->dev, "%s: not opened\n", __func__);
+ return;
+ }
+
+ pdphy->is_opened = false;
+ pdphy_enable_irq(pdphy, false);
+
+ pdphy->tx_status = -ESHUTDOWN;
+
+ wake_up_all(&pdphy->tx_waitq);
+
+ pdphy_reg_write(pdphy, USB_PDPHY_BIST_MODE, 0);
+ pdphy->in_test_data_mode = false;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, 0);
+ if (ret)
+ return;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_EN_CONTROL, 0);
+ if (ret)
+ return;
+
+ pdphy_enable_power(pdphy, false);
+}
+EXPORT_SYMBOL(pd_phy_close);
+
+static irqreturn_t pdphy_msg_tx_irq(int irq, void *data)
+{
+ struct usb_pdphy *pdphy = data;
+
+ if (irq == pdphy->msg_tx_irq) {
+ pdphy->msg_tx_cnt++;
+ pdphy->tx_status = 0;
+ } else if (irq == pdphy->msg_tx_discarded_irq) {
+ pdphy->msg_tx_discarded_cnt++;
+ pdphy->tx_status = -EBUSY;
+ } else if (irq == pdphy->msg_tx_failed_irq) {
+ pdphy->msg_tx_failed_cnt++;
+ pdphy->tx_status = -EFAULT;
+ } else {
+ dev_err(pdphy->dev, "spurious irq #%d received\n", irq);
+ return IRQ_NONE;
+ }
+
+ wake_up(&pdphy->tx_waitq);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pdphy_msg_rx_discarded_irq(int irq, void *data)
+{
+ struct usb_pdphy *pdphy = data;
+
+ pdphy->msg_rx_discarded_cnt++;
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pdphy_sig_rx_irq_thread(int irq, void *data)
+{
+ u8 rx_status, frame_type;
+ int ret;
+ struct usb_pdphy *pdphy = data;
+
+ pdphy->sig_rx_cnt++;
+
+ ret = pdphy_reg_read(pdphy, &rx_status, USB_PDPHY_RX_STATUS, 1);
+ if (ret)
+ goto done;
+
+ frame_type = rx_status & RX_FRAME_TYPE;
+ if (frame_type != HARD_RESET_SIG) {
+ dev_err(pdphy->dev, "%s:unsupported frame type %d\n",
+ __func__, frame_type);
+ goto done;
+ }
+
+ /* Frame filter is reconfigured in pd_phy_open() */
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_FRAME_FILTER, 0);
+
+ if (pdphy->signal_cb)
+ pdphy->signal_cb(pdphy->usbpd, frame_type);
+
+done:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pdphy_sig_tx_irq_thread(int irq, void *data)
+{
+ struct usb_pdphy *pdphy = data;
+
+ /* in case of exit from BIST Carrier Mode 2, clear BIST_MODE */
+ pdphy_reg_write(pdphy, USB_PDPHY_BIST_MODE, 0);
+
+ pdphy->sig_tx_cnt++;
+ pdphy->tx_status = 0;
+ wake_up(&pdphy->tx_waitq);
+
+ return IRQ_HANDLED;
+}
+
+static int pd_phy_bist_mode(u8 bist_mode)
+{
+ struct usb_pdphy *pdphy = __pdphy;
+
+ dev_dbg(pdphy->dev, "%s: enter BIST mode %d\n", __func__, bist_mode);
+
+ pdphy_reg_write(pdphy, USB_PDPHY_BIST_MODE, 0);
+
+ udelay(5);
+
+ return pdphy_masked_write(pdphy, USB_PDPHY_BIST_MODE,
+ BIST_MODE_MASK | BIST_ENABLE, bist_mode | BIST_ENABLE);
+}
+
+static irqreturn_t pdphy_msg_rx_irq(int irq, void *data)
+{
+ struct usb_pdphy *pdphy = data;
+
+ pdphy->rx_busy = true;
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t pdphy_msg_rx_irq_thread(int irq, void *data)
+{
+ u8 size, rx_status, frame_type;
+ u8 buf[32];
+ int ret;
+ struct usb_pdphy *pdphy = data;
+
+ pdphy->msg_rx_cnt++;
+
+ ret = pdphy_reg_read(pdphy, &size, USB_PDPHY_RX_SIZE, 1);
+ if (ret)
+ goto done;
+
+ if (!size || size > 31) {
+ dev_err(pdphy->dev, "%s: invalid size %d\n", __func__, size);
+ goto done;
+ }
+
+ ret = pdphy_reg_read(pdphy, &rx_status, USB_PDPHY_RX_STATUS, 1);
+ if (ret)
+ goto done;
+
+ frame_type = rx_status & RX_FRAME_TYPE;
+ if (frame_type != SOP_MSG) {
+ dev_err(pdphy->dev, "%s:unsupported frame type %d\n",
+ __func__, frame_type);
+ goto done;
+ }
+
+ ret = pdphy_reg_read(pdphy, buf, USB_PDPHY_RX_BUFFER, size + 1);
+ if (ret)
+ goto done;
+
+ /* ack to change ownership of rx buffer back to PDPHY RX HW */
+ pdphy_reg_write(pdphy, USB_PDPHY_RX_ACKNOWLEDGE, 0);
+
+ if (((buf[0] & 0xf) == PD_MSG_BIST) && size >= 5) { /* BIST */
+ u8 mode = buf[5] >> 4; /* [31:28] of 1st data object */
+
+ pd_phy_bist_mode(mode);
+ pdphy_reg_write(pdphy, USB_PDPHY_RX_ACKNOWLEDGE, 0);
+
+ if (mode == PD_BIST_TEST_DATA_MODE) {
+ pdphy->in_test_data_mode = true;
+ disable_irq_nosync(irq);
+ }
+ goto done;
+ }
+
+ if (pdphy->msg_rx_cb)
+ pdphy->msg_rx_cb(pdphy->usbpd, frame_type, buf, size + 1);
+
+ print_hex_dump_debug("rx msg:", DUMP_PREFIX_NONE, 32, 4, buf, size + 1,
+ false);
+ pdphy->rx_bytes += size + 1;
+done:
+ pdphy->rx_busy = false;
+ return IRQ_HANDLED;
+}
+
+static int pdphy_request_irq(struct usb_pdphy *pdphy,
+ struct device_node *node,
+ int *irq_num, const char *irq_name,
+ irqreturn_t (irq_handler)(int irq, void *data),
+ irqreturn_t (thread_fn)(int irq, void *data),
+ int flags)
+{
+ int ret;
+
+ *irq_num = of_irq_get_byname(node, irq_name);
+ if (*irq_num < 0) {
+ dev_err(pdphy->dev, "Unable to get %s irqn", irq_name);
+ ret = -ENXIO;
+ }
+
+ irq_set_status_flags(*irq_num, IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(pdphy->dev, *irq_num, irq_handler,
+ thread_fn, flags, irq_name, pdphy);
+ if (ret < 0) {
+ dev_err(pdphy->dev, "Unable to request %s irq: %dn",
+ irq_name, ret);
+ ret = -ENXIO;
+ }
+
+ return 0;
+}
+
+static int pdphy_probe(struct platform_device *pdev)
+{
+ int ret;
+ unsigned int base;
+ struct usb_pdphy *pdphy;
+
+ pdphy = devm_kzalloc(&pdev->dev, sizeof(*pdphy), GFP_KERNEL);
+ if (!pdphy)
+ return -ENOMEM;
+
+ pdphy->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!pdphy->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ dev_set_drvdata(&pdev->dev, pdphy);
+
+ ret = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to get reg base address ret = %d\n",
+ ret);
+ return ret;
+ }
+
+ pdphy->base = base;
+ pdphy->dev = &pdev->dev;
+
+ init_waitqueue_head(&pdphy->tx_waitq);
+
+ pdphy->vdd_pdphy = devm_regulator_get(&pdev->dev, "vdd-pdphy");
+ if (IS_ERR(pdphy->vdd_pdphy)) {
+ dev_err(&pdev->dev, "unable to get vdd-pdphy\n");
+ return PTR_ERR(pdphy->vdd_pdphy);
+ }
+
+ ret = pdphy_request_irq(pdphy, pdev->dev.of_node,
+ &pdphy->sig_tx_irq, "sig-tx", NULL,
+ pdphy_sig_tx_irq_thread, (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (ret < 0)
+ return ret;
+
+ ret = pdphy_request_irq(pdphy, pdev->dev.of_node,
+ &pdphy->sig_rx_irq, "sig-rx", NULL,
+ pdphy_sig_rx_irq_thread, (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (ret < 0)
+ return ret;
+
+ ret = pdphy_request_irq(pdphy, pdev->dev.of_node,
+ &pdphy->msg_tx_irq, "msg-tx", pdphy_msg_tx_irq,
+ NULL, (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (ret < 0)
+ return ret;
+
+ ret = pdphy_request_irq(pdphy, pdev->dev.of_node,
+ &pdphy->msg_rx_irq, "msg-rx", pdphy_msg_rx_irq,
+ pdphy_msg_rx_irq_thread, (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (ret < 0)
+ return ret;
+
+ ret = pdphy_request_irq(pdphy, pdev->dev.of_node,
+ &pdphy->msg_tx_failed_irq, "msg-tx-failed", pdphy_msg_tx_irq,
+ NULL, (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (ret < 0)
+ return ret;
+
+ ret = pdphy_request_irq(pdphy, pdev->dev.of_node,
+ &pdphy->msg_tx_discarded_irq, "msg-tx-discarded",
+ pdphy_msg_tx_irq, NULL,
+ (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (ret < 0)
+ return ret;
+
+ ret = pdphy_request_irq(pdphy, pdev->dev.of_node,
+ &pdphy->msg_rx_discarded_irq, "msg-rx-discarded",
+ pdphy_msg_rx_discarded_irq, NULL,
+ (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (ret < 0)
+ return ret;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_SEC_ACCESS, 0xA5);
+ if (ret)
+ return ret;
+
+ ret = pdphy_reg_write(pdphy, USB_PDPHY_TRIM_3, 0x2);
+ if (ret)
+ return ret;
+
+ /* usbpd_create() could call back to us, so have __pdphy ready */
+ __pdphy = pdphy;
+
+ pdphy->usbpd = usbpd_create(&pdev->dev);
+ if (IS_ERR(pdphy->usbpd)) {
+ dev_err(&pdev->dev, "usbpd_create failed: %ld\n",
+ PTR_ERR(pdphy->usbpd));
+ __pdphy = NULL;
+ return PTR_ERR(pdphy->usbpd);
+ }
+
+ pdphy_create_debugfs_entries(pdphy);
+
+ return 0;
+}
+
+static int pdphy_remove(struct platform_device *pdev)
+{
+ struct usb_pdphy *pdphy = platform_get_drvdata(pdev);
+
+ debugfs_remove_recursive(pdphy->debug_root);
+ usbpd_destroy(pdphy->usbpd);
+
+ if (pdphy->is_opened)
+ pd_phy_close();
+
+ __pdphy = NULL;
+
+ return 0;
+}
+
+static void pdphy_shutdown(struct platform_device *pdev)
+{
+ struct usb_pdphy *pdphy = platform_get_drvdata(pdev);
+
+ /* let protocol engine shutdown the pdphy synchronously */
+ if (pdphy->shutdown_cb)
+ pdphy->shutdown_cb(pdphy->usbpd);
+}
+
+static const struct of_device_id pdphy_match_table[] = {
+ {
+ .compatible = "qcom,qpnp-pdphy",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pdphy_match_table);
+
+static struct platform_driver pdphy_driver = {
+ .driver = {
+ .name = "qpnp-pdphy",
+ .of_match_table = pdphy_match_table,
+ },
+ .probe = pdphy_probe,
+ .remove = pdphy_remove,
+ .shutdown = pdphy_shutdown,
+};
+
+module_platform_driver(pdphy_driver);
+
+MODULE_DESCRIPTION("QPNP PD PHY Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:qpnp-pdphy");
diff --git a/drivers/usb/pd/usbpd.h b/drivers/usb/pd/usbpd.h
new file mode 100644
index 000000000000..9b6053e940e9
--- /dev/null
+++ b/drivers/usb/pd/usbpd.h
@@ -0,0 +1,106 @@
+/* Copyright (c) 2016-2017, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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 _USBPD_H
+#define _USBPD_H
+
+#include <linux/device.h>
+
+struct usbpd;
+
+#if IS_ENABLED(CONFIG_USB_PD_POLICY)
+struct usbpd *usbpd_create(struct device *parent);
+void usbpd_destroy(struct usbpd *pd);
+#else
+static inline struct usbpd *usbpd_create(struct device *parent)
+{
+ return ERR_PTR(-ENODEV);
+}
+static inline void usbpd_destroy(struct usbpd *pd) { }
+#endif
+
+enum data_role {
+ DR_NONE = -1,
+ DR_UFP = 0,
+ DR_DFP = 1,
+};
+
+enum power_role {
+ PR_NONE = -1,
+ PR_SINK = 0,
+ PR_SRC = 1,
+};
+
+enum pd_sig_type {
+ HARD_RESET_SIG = 0,
+ CABLE_RESET_SIG,
+};
+
+enum pd_sop_type {
+ SOP_MSG = 0,
+ SOPI_MSG,
+ SOPII_MSG,
+};
+
+enum pd_spec_rev {
+ USBPD_REV_20 = 1,
+ USBPD_REV_30 = 2,
+};
+
+/* enable msg and signal to be received by phy */
+#define FRAME_FILTER_EN_SOP BIT(0)
+#define FRAME_FILTER_EN_HARD_RESET BIT(5)
+
+struct pd_phy_params {
+ void (*signal_cb)(struct usbpd *pd, enum pd_sig_type sig);
+ void (*msg_rx_cb)(struct usbpd *pd, enum pd_sop_type sop,
+ u8 *buf, size_t len);
+ void (*shutdown_cb)(struct usbpd *pd);
+ enum data_role data_role;
+ enum power_role power_role;
+ u8 frame_filter_val;
+};
+
+#if IS_ENABLED(CONFIG_QPNP_USB_PDPHY)
+int pd_phy_open(struct pd_phy_params *params);
+int pd_phy_signal(enum pd_sig_type sig);
+int pd_phy_write(u16 hdr, const u8 *data, size_t data_len,
+ enum pd_sop_type sop);
+int pd_phy_update_roles(enum data_role dr, enum power_role pr);
+void pd_phy_close(void);
+#else
+static inline int pd_phy_open(struct pd_phy_params *params)
+{
+ return -ENODEV;
+}
+
+static inline int pd_phy_signal(enum pd_sig_type type)
+{
+ return -ENODEV;
+}
+
+static inline int pd_phy_write(u16 hdr, const u8 *data, size_t data_len,
+ enum pd_sop_type sop)
+{
+ return -ENODEV;
+}
+
+static inline int pd_phy_update_roles(enum data_role dr, enum power_role pr)
+{
+ return -ENODEV;
+}
+
+static inline void pd_phy_close(void)
+{
+}
+#endif
+#endif /* _USBPD_H */