summaryrefslogtreecommitdiff
path: root/drivers/usb/typec
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r--drivers/usb/typec/Kconfig24
-rw-r--r--drivers/usb/typec/Makefile2
-rw-r--r--drivers/usb/typec/altmodes/displayport.c2
-rw-r--r--drivers/usb/typec/class.c15
-rw-r--r--drivers/usb/typec/hd3ss3220.c18
-rw-r--r--drivers/usb/typec/mux/Kconfig1
-rw-r--r--drivers/usb/typec/mux/intel_pmc_mux.c207
-rw-r--r--drivers/usb/typec/qcom-pmic-typec.c262
-rw-r--r--drivers/usb/typec/stusb160x.c875
-rw-r--r--drivers/usb/typec/tcpm/Kconfig14
-rw-r--r--drivers/usb/typec/tcpm/Makefile14
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c113
-rw-r--r--drivers/usb/typec/tcpm/tcpci.h25
-rw-r--r--drivers/usb/typec/tcpm/tcpci_maxim.c503
-rw-r--r--drivers/usb/typec/tcpm/tcpci_mt6360.c212
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c362
16 files changed, 2543 insertions, 106 deletions
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 559dd06117e7..6c5908a37ee8 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -73,6 +73,30 @@ config TYPEC_TPS6598X
If you choose to build this driver as a dynamically linked module, the
module will be called tps6598x.ko.
+config TYPEC_STUSB160X
+ tristate "STMicroelectronics STUSB160x Type-C controller driver"
+ depends on I2C
+ depends on REGMAP_I2C
+ depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH
+ help
+ Say Y or M here if your system has STMicroelectronics STUSB160x
+ Type-C port controller.
+
+ If you choose to build this driver as a dynamically linked module, the
+ module will be called stusb160x.ko.
+
+config TYPEC_QCOM_PMIC
+ tristate "Qualcomm PMIC USB Type-C driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ help
+ Driver for supporting role switch over the Qualcomm PMIC. This will
+ handle the USB Type-C role and orientation detection reported by the
+ QCOM PMIC if the PMIC has the capability to handle USB Type-C
+ detection.
+
+ It will also enable the VBUS output to connected devices when a
+ DFP connection is made.
+
source "drivers/usb/typec/mux/Kconfig"
source "drivers/usb/typec/altmodes/Kconfig"
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 7753a5c3cd46..d03b48c4b864 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -6,4 +6,6 @@ obj-$(CONFIG_TYPEC_TCPM) += tcpm/
obj-$(CONFIG_TYPEC_UCSI) += ucsi/
obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o
obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o
+obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o
+obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o
obj-$(CONFIG_TYPEC) += mux/
diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
index 7b20073d7fc0..e62e5e3da01e 100644
--- a/drivers/usb/typec/altmodes/displayport.c
+++ b/drivers/usb/typec/altmodes/displayport.c
@@ -190,7 +190,7 @@ static void dp_altmode_work(struct work_struct *work)
switch (dp->state) {
case DP_STATE_ENTER:
ret = typec_altmode_enter(dp->alt, NULL);
- if (ret)
+ if (ret && ret != -EBUSY)
dev_err(&dp->alt->dev, "failed to enter mode\n");
break;
case DP_STATE_UPDATE:
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 02655694f200..35eec707cb51 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -1449,6 +1449,21 @@ void typec_set_pwr_opmode(struct typec_port *port,
EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
/**
+ * typec_find_pwr_opmode - Get the typec power operation mode capability
+ * @name: power operation mode string
+ *
+ * This routine is used to find the typec_pwr_opmode by its string @name.
+ *
+ * Returns typec_pwr_opmode if success, otherwise negative error code.
+ */
+int typec_find_pwr_opmode(const char *name)
+{
+ return match_string(typec_pwr_opmodes,
+ ARRAY_SIZE(typec_pwr_opmodes), name);
+}
+EXPORT_SYMBOL_GPL(typec_find_pwr_opmode);
+
+/**
* typec_find_orientation - Convert orientation string to enum typec_orientation
* @name: Orientation string
*
diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c
index 323dfa8160ab..f633ec15b1a1 100644
--- a/drivers/usb/typec/hd3ss3220.c
+++ b/drivers/usb/typec/hd3ss3220.c
@@ -155,7 +155,7 @@ static int hd3ss3220_probe(struct i2c_client *client,
{
struct typec_capability typec_cap = { };
struct hd3ss3220 *hd3ss3220;
- struct fwnode_handle *connector;
+ struct fwnode_handle *connector, *ep;
int ret;
unsigned int data;
@@ -173,11 +173,21 @@ static int hd3ss3220_probe(struct i2c_client *client,
hd3ss3220_set_source_pref(hd3ss3220,
HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT);
+ /* For backward compatibility check the connector child node first */
connector = device_get_named_child_node(hd3ss3220->dev, "connector");
- if (!connector)
- return -ENODEV;
+ if (connector) {
+ hd3ss3220->role_sw = fwnode_usb_role_switch_get(connector);
+ } else {
+ ep = fwnode_graph_get_next_endpoint(dev_fwnode(hd3ss3220->dev), NULL);
+ if (!ep)
+ return -ENODEV;
+ connector = fwnode_graph_get_remote_port_parent(ep);
+ fwnode_handle_put(ep);
+ if (!connector)
+ return -ENODEV;
+ hd3ss3220->role_sw = usb_role_switch_get(hd3ss3220->dev);
+ }
- hd3ss3220->role_sw = fwnode_usb_role_switch_get(connector);
if (IS_ERR(hd3ss3220->role_sw)) {
ret = PTR_ERR(hd3ss3220->role_sw);
goto err_put_fwnode;
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
index a4dbd11f8ee2..edead555835e 100644
--- a/drivers/usb/typec/mux/Kconfig
+++ b/drivers/usb/typec/mux/Kconfig
@@ -11,6 +11,7 @@ config TYPEC_MUX_PI3USB30532
config TYPEC_MUX_INTEL_PMC
tristate "Intel PMC mux control"
+ depends on ACPI
depends on INTEL_SCU_IPC
select USB_ROLE_SWITCH
help
diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c
index 676b525c2a66..d7f63b74c6b1 100644
--- a/drivers/usb/typec/mux/intel_pmc_mux.c
+++ b/drivers/usb/typec/mux/intel_pmc_mux.c
@@ -80,10 +80,48 @@ enum {
#define PMC_USB_DP_HPD_LVL BIT(4)
#define PMC_USB_DP_HPD_IRQ BIT(5)
+/*
+ * Input Output Manager (IOM) PORT STATUS
+ */
+#define IOM_PORT_STATUS_OFFSET 0x560
+
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_MASK GENMASK(9, 6)
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_SHIFT 6
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_USB 0x03
+/* activity type: Safe Mode */
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_SAFE_MODE 0x04
+/* activity type: Display Port */
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_DP 0x05
+/* activity type: Display Port Multi Function Device */
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_DP_MFD 0x06
+/* activity type: Thunderbolt */
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_TBT 0x07
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_ALT_MODE_USB 0x0c
+#define IOM_PORT_STATUS_ACTIVITY_TYPE_ALT_MODE_TBT_USB 0x0d
+/* Upstream Facing Port Information */
+#define IOM_PORT_STATUS_UFP BIT(10)
+/* Display Port Hot Plug Detect status */
+#define IOM_PORT_STATUS_DHPD_HPD_STATUS_MASK GENMASK(13, 12)
+#define IOM_PORT_STATUS_DHPD_HPD_STATUS_SHIFT 12
+#define IOM_PORT_STATUS_DHPD_HPD_STATUS_ASSERT 0x01
+#define IOM_PORT_STATUS_DHPD_HPD_SOURCE_TBT BIT(14)
+#define IOM_PORT_STATUS_CONNECTED BIT(31)
+
+#define IOM_PORT_ACTIVITY_IS(_status_, _type_) \
+ ((((_status_) & IOM_PORT_STATUS_ACTIVITY_TYPE_MASK) >> \
+ IOM_PORT_STATUS_ACTIVITY_TYPE_SHIFT) == \
+ (IOM_PORT_STATUS_ACTIVITY_TYPE_##_type_))
+
+#define IOM_PORT_HPD_ASSERTED(_status_) \
+ ((((_status_) & IOM_PORT_STATUS_DHPD_HPD_STATUS_MASK) >> \
+ IOM_PORT_STATUS_DHPD_HPD_STATUS_SHIFT) & \
+ IOM_PORT_STATUS_DHPD_HPD_STATUS_ASSERT)
+
struct pmc_usb;
struct pmc_usb_port {
int num;
+ u32 iom_status;
struct pmc_usb *pmc;
struct typec_mux *typec_mux;
struct typec_switch *typec_sw;
@@ -104,8 +142,21 @@ struct pmc_usb {
struct device *dev;
struct intel_scu_ipc_dev *ipc;
struct pmc_usb_port *port;
+ struct acpi_device *iom_adev;
+ void __iomem *iom_base;
};
+static void update_port_status(struct pmc_usb_port *port)
+{
+ u8 port_num;
+
+ /* SoC expects the USB Type-C port numbers to start with 0 */
+ port_num = port->usb3_port - 1;
+
+ port->iom_status = readl(port->pmc->iom_base + IOM_PORT_STATUS_OFFSET +
+ port_num * sizeof(u32));
+}
+
static int sbu_orientation(struct pmc_usb_port *port)
{
if (port->sbu_orientation)
@@ -148,18 +199,17 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len)
}
static int
-pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_mux_state *state)
+pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_displayport_data *dp)
{
- struct typec_displayport_data *data = state->data;
u8 msg[2] = { };
msg[0] = PMC_USB_DP_HPD;
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
- if (data->status & DP_STATUS_IRQ_HPD)
+ if (dp->status & DP_STATUS_IRQ_HPD)
msg[1] = PMC_USB_DP_HPD_IRQ;
- if (data->status & DP_STATUS_HPD_STATE)
+ if (dp->status & DP_STATUS_HPD_STATE)
msg[1] |= PMC_USB_DP_HPD_LVL;
return pmc_usb_command(port, msg, sizeof(msg));
@@ -172,8 +222,15 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state)
struct altmode_req req = { };
int ret;
- if (data->status & DP_STATUS_IRQ_HPD)
- return pmc_usb_mux_dp_hpd(port, state);
+ if (IOM_PORT_ACTIVITY_IS(port->iom_status, DP) ||
+ IOM_PORT_ACTIVITY_IS(port->iom_status, DP_MFD)) {
+ if (IOM_PORT_HPD_ASSERTED(port->iom_status) &&
+ (!(data->status & DP_STATUS_IRQ_HPD) &&
+ data->status & DP_STATUS_HPD_STATE))
+ return 0;
+
+ return pmc_usb_mux_dp_hpd(port, state->data);
+ }
req.usage = PMC_USB_ALT_MODE;
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
@@ -189,8 +246,8 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state)
if (ret)
return ret;
- if (data->status & DP_STATUS_HPD_STATE)
- return pmc_usb_mux_dp_hpd(port, state);
+ if (data->status & (DP_STATUS_IRQ_HPD | DP_STATUS_HPD_STATE))
+ return pmc_usb_mux_dp_hpd(port, state->data);
return 0;
}
@@ -202,6 +259,10 @@ pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state)
u8 cable_speed = TBT_CABLE_SPEED(data->cable_mode);
struct altmode_req req = { };
+ if (IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) ||
+ IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB))
+ return 0;
+
req.usage = PMC_USB_ALT_MODE;
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT;
@@ -233,6 +294,10 @@ pmc_usb_mux_usb4(struct pmc_usb_port *port, struct typec_mux_state *state)
struct altmode_req req = { };
u8 cable_speed;
+ if (IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) ||
+ IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB))
+ return 0;
+
req.usage = PMC_USB_ALT_MODE;
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT;
@@ -267,34 +332,61 @@ static int pmc_usb_mux_safe_state(struct pmc_usb_port *port)
{
u8 msg;
+ if (IOM_PORT_ACTIVITY_IS(port->iom_status, SAFE_MODE))
+ return 0;
+
msg = PMC_USB_SAFE_MODE;
msg |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
return pmc_usb_command(port, &msg, sizeof(msg));
}
-static int pmc_usb_connect(struct pmc_usb_port *port)
+static int pmc_usb_disconnect(struct pmc_usb_port *port)
{
+ struct typec_displayport_data data = { };
u8 msg[2];
- msg[0] = PMC_USB_CONNECT;
+ if (!(port->iom_status & IOM_PORT_STATUS_CONNECTED))
+ return 0;
+
+ /* Clear DisplayPort HPD if it's still asserted. */
+ if (IOM_PORT_HPD_ASSERTED(port->iom_status))
+ pmc_usb_mux_dp_hpd(port, &data);
+
+ msg[0] = PMC_USB_DISCONNECT;
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT;
- msg[1] |= hsl_orientation(port) << PMC_USB_MSG_ORI_HSL_SHIFT;
- msg[1] |= sbu_orientation(port) << PMC_USB_MSG_ORI_AUX_SHIFT;
return pmc_usb_command(port, msg, sizeof(msg));
}
-static int pmc_usb_disconnect(struct pmc_usb_port *port)
+static int pmc_usb_connect(struct pmc_usb_port *port, enum usb_role role)
{
+ u8 ufp = role == USB_ROLE_DEVICE ? 1 : 0;
u8 msg[2];
+ int ret;
- msg[0] = PMC_USB_DISCONNECT;
+ if (port->orientation == TYPEC_ORIENTATION_NONE)
+ return -EINVAL;
+
+ if (port->iom_status & IOM_PORT_STATUS_CONNECTED) {
+ if (port->role == role || port->role == USB_ROLE_NONE)
+ return 0;
+
+ /* Role swap */
+ ret = pmc_usb_disconnect(port);
+ if (ret)
+ return ret;
+ }
+
+ msg[0] = PMC_USB_CONNECT;
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT;
+ msg[1] |= ufp << PMC_USB_MSG_UFP_SHIFT;
+ msg[1] |= hsl_orientation(port) << PMC_USB_MSG_ORI_HSL_SHIFT;
+ msg[1] |= sbu_orientation(port) << PMC_USB_MSG_ORI_AUX_SHIFT;
return pmc_usb_command(port, msg, sizeof(msg));
}
@@ -304,13 +396,15 @@ pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
{
struct pmc_usb_port *port = typec_mux_get_drvdata(mux);
+ update_port_status(port);
+
if (port->orientation == TYPEC_ORIENTATION_NONE || port->role == USB_ROLE_NONE)
return 0;
if (state->mode == TYPEC_STATE_SAFE)
return pmc_usb_mux_safe_state(port);
if (state->mode == TYPEC_STATE_USB)
- return pmc_usb_connect(port);
+ return pmc_usb_connect(port, port->role);
if (state->alt) {
switch (state->alt->svid) {
@@ -325,7 +419,7 @@ pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
/* REVISIT: Try with usb3_port set to 0? */
break;
case TYPEC_MODE_USB3:
- return pmc_usb_connect(port);
+ return pmc_usb_connect(port, port->role);
case TYPEC_MODE_USB4:
return pmc_usb_mux_usb4(port, state);
}
@@ -339,38 +433,28 @@ static int pmc_usb_set_orientation(struct typec_switch *sw,
{
struct pmc_usb_port *port = typec_switch_get_drvdata(sw);
- if (port->orientation == orientation)
- return 0;
+ update_port_status(port);
port->orientation = orientation;
- if (port->role) {
- if (orientation == TYPEC_ORIENTATION_NONE)
- return pmc_usb_disconnect(port);
- else
- return pmc_usb_connect(port);
- }
-
return 0;
}
static int pmc_usb_set_role(struct usb_role_switch *sw, enum usb_role role)
{
struct pmc_usb_port *port = usb_role_switch_get_drvdata(sw);
+ int ret;
- if (port->role == role)
- return 0;
+ update_port_status(port);
- port->role = role;
+ if (role == USB_ROLE_NONE)
+ ret = pmc_usb_disconnect(port);
+ else
+ ret = pmc_usb_connect(port, role);
- if (port->orientation) {
- if (role == USB_ROLE_NONE)
- return pmc_usb_disconnect(port);
- else
- return pmc_usb_connect(port);
- }
+ port->role = role;
- return 0;
+ return ret;
}
static int pmc_usb_register_port(struct pmc_usb *pmc, int index,
@@ -444,6 +528,45 @@ err_unregister_switch:
return ret;
}
+static int is_memory(struct acpi_resource *res, void *data)
+{
+ struct resource r;
+
+ return !acpi_dev_resource_memory(res, &r);
+}
+
+static int pmc_usb_probe_iom(struct pmc_usb *pmc)
+{
+ struct list_head resource_list;
+ struct resource_entry *rentry;
+ struct acpi_device *adev;
+ int ret;
+
+ adev = acpi_dev_get_first_match_dev("INTC1072", NULL, -1);
+ if (!adev)
+ return -ENODEV;
+
+ INIT_LIST_HEAD(&resource_list);
+ ret = acpi_dev_get_resources(adev, &resource_list, is_memory, NULL);
+ if (ret < 0)
+ return ret;
+
+ rentry = list_first_entry_or_null(&resource_list, struct resource_entry, node);
+ if (rentry)
+ pmc->iom_base = devm_ioremap_resource(pmc->dev, rentry->res);
+
+ acpi_dev_free_resource_list(&resource_list);
+
+ if (!pmc->iom_base) {
+ put_device(&adev->dev);
+ return -ENOMEM;
+ }
+
+ pmc->iom_adev = adev;
+
+ return 0;
+}
+
static int pmc_usb_probe(struct platform_device *pdev)
{
struct fwnode_handle *fwnode = NULL;
@@ -458,6 +581,12 @@ static int pmc_usb_probe(struct platform_device *pdev)
device_for_each_child_node(&pdev->dev, fwnode)
pmc->num_ports++;
+ /* The IOM microcontroller has a limitation of max 4 ports. */
+ if (pmc->num_ports > 4) {
+ dev_err(&pdev->dev, "driver limited to 4 ports\n");
+ return -ERANGE;
+ }
+
pmc->port = devm_kcalloc(&pdev->dev, pmc->num_ports,
sizeof(struct pmc_usb_port), GFP_KERNEL);
if (!pmc->port)
@@ -469,6 +598,10 @@ static int pmc_usb_probe(struct platform_device *pdev)
pmc->dev = &pdev->dev;
+ ret = pmc_usb_probe_iom(pmc);
+ if (ret)
+ return ret;
+
/*
* For every physical USB connector (USB2 and USB3 combo) there is a
* child ACPI device node under the PMC mux ACPI device object.
@@ -494,6 +627,8 @@ err_remove_ports:
usb_role_switch_unregister(pmc->port[i].usb_sw);
}
+ put_device(&pmc->iom_adev->dev);
+
return ret;
}
@@ -508,6 +643,8 @@ static int pmc_usb_remove(struct platform_device *pdev)
usb_role_switch_unregister(pmc->port[i].usb_sw);
}
+ put_device(&pmc->iom_adev->dev);
+
return 0;
}
diff --git a/drivers/usb/typec/qcom-pmic-typec.c b/drivers/usb/typec/qcom-pmic-typec.c
new file mode 100644
index 000000000000..a0454a80c4a2
--- /dev/null
+++ b/drivers/usb/typec/qcom-pmic-typec.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/usb/role.h>
+#include <linux/usb/typec_mux.h>
+
+#define TYPEC_MISC_STATUS 0xb
+#define CC_ATTACHED BIT(0)
+#define CC_ORIENTATION BIT(1)
+#define SNK_SRC_MODE BIT(6)
+#define TYPEC_MODE_CFG 0x44
+#define TYPEC_DISABLE_CMD BIT(0)
+#define EN_SNK_ONLY BIT(1)
+#define EN_SRC_ONLY BIT(2)
+#define TYPEC_VCONN_CONTROL 0x46
+#define VCONN_EN_SRC BIT(0)
+#define VCONN_EN_VAL BIT(1)
+#define TYPEC_EXIT_STATE_CFG 0x50
+#define SEL_SRC_UPPER_REF BIT(2)
+#define TYPEC_INTR_EN_CFG_1 0x5e
+#define TYPEC_INTR_EN_CFG_1_MASK GENMASK(7, 0)
+
+struct qcom_pmic_typec {
+ struct device *dev;
+ struct regmap *regmap;
+ u32 base;
+
+ struct typec_port *port;
+ struct usb_role_switch *role_sw;
+
+ struct regulator *vbus_reg;
+ bool vbus_enabled;
+};
+
+static void qcom_pmic_typec_enable_vbus_regulator(struct qcom_pmic_typec
+ *qcom_usb, bool enable)
+{
+ int ret;
+
+ if (enable == qcom_usb->vbus_enabled)
+ return;
+
+ if (enable) {
+ ret = regulator_enable(qcom_usb->vbus_reg);
+ if (ret)
+ return;
+ } else {
+ ret = regulator_disable(qcom_usb->vbus_reg);
+ if (ret)
+ return;
+ }
+ qcom_usb->vbus_enabled = enable;
+}
+
+static void qcom_pmic_typec_check_connection(struct qcom_pmic_typec *qcom_usb)
+{
+ enum typec_orientation orientation;
+ enum usb_role role;
+ unsigned int stat;
+ bool enable_vbus;
+
+ regmap_read(qcom_usb->regmap, qcom_usb->base + TYPEC_MISC_STATUS,
+ &stat);
+
+ if (stat & CC_ATTACHED) {
+ orientation = (stat & CC_ORIENTATION) ?
+ TYPEC_ORIENTATION_REVERSE :
+ TYPEC_ORIENTATION_NORMAL;
+ typec_set_orientation(qcom_usb->port, orientation);
+
+ role = (stat & SNK_SRC_MODE) ? USB_ROLE_HOST : USB_ROLE_DEVICE;
+ if (role == USB_ROLE_HOST)
+ enable_vbus = true;
+ else
+ enable_vbus = false;
+ } else {
+ role = USB_ROLE_NONE;
+ enable_vbus = false;
+ }
+
+ qcom_pmic_typec_enable_vbus_regulator(qcom_usb, enable_vbus);
+ usb_role_switch_set_role(qcom_usb->role_sw, role);
+}
+
+static irqreturn_t qcom_pmic_typec_interrupt(int irq, void *_qcom_usb)
+{
+ struct qcom_pmic_typec *qcom_usb = _qcom_usb;
+
+ qcom_pmic_typec_check_connection(qcom_usb);
+ return IRQ_HANDLED;
+}
+
+static void qcom_pmic_typec_typec_hw_init(struct qcom_pmic_typec *qcom_usb,
+ enum typec_port_type type)
+{
+ u8 mode = 0;
+
+ regmap_update_bits(qcom_usb->regmap,
+ qcom_usb->base + TYPEC_INTR_EN_CFG_1,
+ TYPEC_INTR_EN_CFG_1_MASK, 0);
+
+ if (type == TYPEC_PORT_SRC)
+ mode = EN_SRC_ONLY;
+ else if (type == TYPEC_PORT_SNK)
+ mode = EN_SNK_ONLY;
+
+ regmap_update_bits(qcom_usb->regmap, qcom_usb->base + TYPEC_MODE_CFG,
+ EN_SNK_ONLY | EN_SRC_ONLY, mode);
+
+ regmap_update_bits(qcom_usb->regmap,
+ qcom_usb->base + TYPEC_VCONN_CONTROL,
+ VCONN_EN_SRC | VCONN_EN_VAL, VCONN_EN_SRC);
+ regmap_update_bits(qcom_usb->regmap,
+ qcom_usb->base + TYPEC_EXIT_STATE_CFG,
+ SEL_SRC_UPPER_REF, SEL_SRC_UPPER_REF);
+}
+
+static int qcom_pmic_typec_probe(struct platform_device *pdev)
+{
+ struct qcom_pmic_typec *qcom_usb;
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *fwnode;
+ struct typec_capability cap;
+ const char *buf;
+ int ret, irq, role;
+ u32 reg;
+
+ ret = device_property_read_u32(dev, "reg", &reg);
+ if (ret < 0) {
+ dev_err(dev, "missing base address\n");
+ return ret;
+ }
+
+ qcom_usb = devm_kzalloc(dev, sizeof(*qcom_usb), GFP_KERNEL);
+ if (!qcom_usb)
+ return -ENOMEM;
+
+ qcom_usb->dev = dev;
+ qcom_usb->base = reg;
+
+ qcom_usb->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!qcom_usb->regmap) {
+ dev_err(dev, "Failed to get regmap\n");
+ return -EINVAL;
+ }
+
+ qcom_usb->vbus_reg = devm_regulator_get(qcom_usb->dev, "usb_vbus");
+ if (IS_ERR(qcom_usb->vbus_reg))
+ return PTR_ERR(qcom_usb->vbus_reg);
+
+ fwnode = device_get_named_child_node(dev, "connector");
+ if (!fwnode)
+ return -EINVAL;
+
+ ret = fwnode_property_read_string(fwnode, "power-role", &buf);
+ if (!ret) {
+ role = typec_find_port_power_role(buf);
+ if (role < 0)
+ role = TYPEC_PORT_SNK;
+ } else {
+ role = TYPEC_PORT_SNK;
+ }
+ cap.type = role;
+
+ ret = fwnode_property_read_string(fwnode, "data-role", &buf);
+ if (!ret) {
+ role = typec_find_port_data_role(buf);
+ if (role < 0)
+ role = TYPEC_PORT_UFP;
+ } else {
+ role = TYPEC_PORT_UFP;
+ }
+ cap.data = role;
+
+ cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+ cap.fwnode = fwnode;
+ qcom_usb->port = typec_register_port(dev, &cap);
+ if (IS_ERR(qcom_usb->port)) {
+ ret = PTR_ERR(qcom_usb->port);
+ dev_err(dev, "Failed to register type c port %d\n", ret);
+ goto err_put_node;
+ }
+ fwnode_handle_put(fwnode);
+
+ qcom_usb->role_sw = fwnode_usb_role_switch_get(dev_fwnode(qcom_usb->dev));
+ if (IS_ERR(qcom_usb->role_sw)) {
+ if (PTR_ERR(qcom_usb->role_sw) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get role switch\n");
+ ret = PTR_ERR(qcom_usb->role_sw);
+ goto err_typec_port;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ goto err_usb_role_sw;
+
+ ret = devm_request_threaded_irq(qcom_usb->dev, irq, NULL,
+ qcom_pmic_typec_interrupt, IRQF_ONESHOT,
+ "qcom-pmic-typec", qcom_usb);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not request IRQ\n");
+ goto err_usb_role_sw;
+ }
+
+ platform_set_drvdata(pdev, qcom_usb);
+ qcom_pmic_typec_typec_hw_init(qcom_usb, cap.type);
+ qcom_pmic_typec_check_connection(qcom_usb);
+
+ return 0;
+
+err_usb_role_sw:
+ usb_role_switch_put(qcom_usb->role_sw);
+err_typec_port:
+ typec_unregister_port(qcom_usb->port);
+err_put_node:
+ fwnode_handle_put(fwnode);
+
+ return ret;
+}
+
+static int qcom_pmic_typec_remove(struct platform_device *pdev)
+{
+ struct qcom_pmic_typec *qcom_usb = platform_get_drvdata(pdev);
+
+ usb_role_switch_set_role(qcom_usb->role_sw, USB_ROLE_NONE);
+ qcom_pmic_typec_enable_vbus_regulator(qcom_usb, 0);
+
+ typec_unregister_port(qcom_usb->port);
+ usb_role_switch_put(qcom_usb->role_sw);
+
+ return 0;
+}
+
+static const struct of_device_id qcom_pmic_typec_table[] = {
+ { .compatible = "qcom,pm8150b-usb-typec" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table);
+
+static struct platform_driver qcom_pmic_typec = {
+ .driver = {
+ .name = "qcom,pmic-typec",
+ .of_match_table = qcom_pmic_typec_table,
+ },
+ .probe = qcom_pmic_typec_probe,
+ .remove = qcom_pmic_typec_remove,
+};
+module_platform_driver(qcom_pmic_typec);
+
+MODULE_DESCRIPTION("QCOM PMIC USB type C driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/typec/stusb160x.c b/drivers/usb/typec/stusb160x.c
new file mode 100644
index 000000000000..ce0bd7b3ad88
--- /dev/null
+++ b/drivers/usb/typec/stusb160x.c
@@ -0,0 +1,875 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * STMicroelectronics STUSB160x Type-C controller family driver
+ *
+ * Copyright (C) 2020, STMicroelectronics
+ * Author(s): Amelie Delaunay <amelie.delaunay@st.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/role.h>
+#include <linux/usb/typec.h>
+
+#define STUSB160X_ALERT_STATUS 0x0B /* RC */
+#define STUSB160X_ALERT_STATUS_MASK_CTRL 0x0C /* RW */
+#define STUSB160X_CC_CONNECTION_STATUS_TRANS 0x0D /* RC */
+#define STUSB160X_CC_CONNECTION_STATUS 0x0E /* RO */
+#define STUSB160X_MONITORING_STATUS_TRANS 0x0F /* RC */
+#define STUSB160X_MONITORING_STATUS 0x10 /* RO */
+#define STUSB160X_CC_OPERATION_STATUS 0x11 /* RO */
+#define STUSB160X_HW_FAULT_STATUS_TRANS 0x12 /* RC */
+#define STUSB160X_HW_FAULT_STATUS 0x13 /* RO */
+#define STUSB160X_CC_CAPABILITY_CTRL 0x18 /* RW */
+#define STUSB160X_CC_VCONN_SWITCH_CTRL 0x1E /* RW */
+#define STUSB160X_VCONN_MONITORING_CTRL 0x20 /* RW */
+#define STUSB160X_VBUS_MONITORING_RANGE_CTRL 0x22 /* RW */
+#define STUSB160X_RESET_CTRL 0x23 /* RW */
+#define STUSB160X_VBUS_DISCHARGE_TIME_CTRL 0x25 /* RW */
+#define STUSB160X_VBUS_DISCHARGE_STATUS 0x26 /* RO */
+#define STUSB160X_VBUS_ENABLE_STATUS 0x27 /* RO */
+#define STUSB160X_CC_POWER_MODE_CTRL 0x28 /* RW */
+#define STUSB160X_VBUS_MONITORING_CTRL 0x2E /* RW */
+#define STUSB1600_REG_MAX 0x2F /* RO - Reserved */
+
+/* STUSB160X_ALERT_STATUS/STUSB160X_ALERT_STATUS_MASK_CTRL bitfields */
+#define STUSB160X_HW_FAULT BIT(4)
+#define STUSB160X_MONITORING BIT(5)
+#define STUSB160X_CC_CONNECTION BIT(6)
+#define STUSB160X_ALL_ALERTS GENMASK(6, 4)
+
+/* STUSB160X_CC_CONNECTION_STATUS_TRANS bitfields */
+#define STUSB160X_CC_ATTACH_TRANS BIT(0)
+
+/* STUSB160X_CC_CONNECTION_STATUS bitfields */
+#define STUSB160X_CC_ATTACH BIT(0)
+#define STUSB160X_CC_VCONN_SUPPLY BIT(1)
+#define STUSB160X_CC_DATA_ROLE(s) (!!((s) & BIT(2)))
+#define STUSB160X_CC_POWER_ROLE(s) (!!((s) & BIT(3)))
+#define STUSB160X_CC_ATTACHED_MODE GENMASK(7, 5)
+
+/* STUSB160X_MONITORING_STATUS_TRANS bitfields */
+#define STUSB160X_VCONN_PRESENCE_TRANS BIT(0)
+#define STUSB160X_VBUS_PRESENCE_TRANS BIT(1)
+#define STUSB160X_VBUS_VSAFE0V_TRANS BIT(2)
+#define STUSB160X_VBUS_VALID_TRANS BIT(3)
+
+/* STUSB160X_MONITORING_STATUS bitfields */
+#define STUSB160X_VCONN_PRESENCE BIT(0)
+#define STUSB160X_VBUS_PRESENCE BIT(1)
+#define STUSB160X_VBUS_VSAFE0V BIT(2)
+#define STUSB160X_VBUS_VALID BIT(3)
+
+/* STUSB160X_CC_OPERATION_STATUS bitfields */
+#define STUSB160X_TYPEC_FSM_STATE GENMASK(4, 0)
+#define STUSB160X_SINK_POWER_STATE GENMASK(6, 5)
+#define STUSB160X_CC_ATTACHED BIT(7)
+
+/* STUSB160X_HW_FAULT_STATUS_TRANS bitfields */
+#define STUSB160X_VCONN_SW_OVP_FAULT_TRANS BIT(0)
+#define STUSB160X_VCONN_SW_OCP_FAULT_TRANS BIT(1)
+#define STUSB160X_VCONN_SW_RVP_FAULT_TRANS BIT(2)
+#define STUSB160X_VPU_VALID_TRANS BIT(4)
+#define STUSB160X_VPU_OVP_FAULT_TRANS BIT(5)
+#define STUSB160X_THERMAL_FAULT BIT(7)
+
+/* STUSB160X_HW_FAULT_STATUS bitfields */
+#define STUSB160X_VCONN_SW_OVP_FAULT_CC2 BIT(0)
+#define STUSB160X_VCONN_SW_OVP_FAULT_CC1 BIT(1)
+#define STUSB160X_VCONN_SW_OCP_FAULT_CC2 BIT(2)
+#define STUSB160X_VCONN_SW_OCP_FAULT_CC1 BIT(3)
+#define STUSB160X_VCONN_SW_RVP_FAULT_CC2 BIT(4)
+#define STUSB160X_VCONN_SW_RVP_FAULT_CC1 BIT(5)
+#define STUSB160X_VPU_VALID BIT(6)
+#define STUSB160X_VPU_OVP_FAULT BIT(7)
+
+/* STUSB160X_CC_CAPABILITY_CTRL bitfields */
+#define STUSB160X_CC_VCONN_SUPPLY_EN BIT(0)
+#define STUSB160X_CC_VCONN_DISCHARGE_EN BIT(4)
+#define STUSB160X_CC_CURRENT_ADVERTISED GENMASK(7, 6)
+
+/* STUSB160X_VCONN_SWITCH_CTRL bitfields */
+#define STUSB160X_CC_VCONN_SWITCH_ILIM GENMASK(3, 0)
+
+/* STUSB160X_VCONN_MONITORING_CTRL bitfields */
+#define STUSB160X_VCONN_UVLO_THRESHOLD BIT(6)
+#define STUSB160X_VCONN_MONITORING_EN BIT(7)
+
+/* STUSB160X_VBUS_MONITORING_RANGE_CTRL bitfields */
+#define STUSB160X_SHIFT_LOW_VBUS_LIMIT GENMASK(3, 0)
+#define STUSB160X_SHIFT_HIGH_VBUS_LIMIT GENMASK(7, 4)
+
+/* STUSB160X_RESET_CTRL bitfields */
+#define STUSB160X_SW_RESET_EN BIT(0)
+
+/* STUSB160X_VBUS_DISCHARGE_TIME_CTRL bitfields */
+#define STUSBXX02_VBUS_DISCHARGE_TIME_TO_PDO GENMASK(3, 0)
+#define STUSB160X_VBUS_DISCHARGE_TIME_TO_0V GENMASK(7, 4)
+
+/* STUSB160X_VBUS_DISCHARGE_STATUS bitfields */
+#define STUSB160X_VBUS_DISCHARGE_EN BIT(7)
+
+/* STUSB160X_VBUS_ENABLE_STATUS bitfields */
+#define STUSB160X_VBUS_SOURCE_EN BIT(0)
+#define STUSB160X_VBUS_SINK_EN BIT(1)
+
+/* STUSB160X_CC_POWER_MODE_CTRL bitfields */
+#define STUSB160X_CC_POWER_MODE GENMASK(2, 0)
+
+/* STUSB160X_VBUS_MONITORING_CTRL bitfields */
+#define STUSB160X_VDD_UVLO_DISABLE BIT(0)
+#define STUSB160X_VBUS_VSAFE0V_THRESHOLD GENMASK(2, 1)
+#define STUSB160X_VBUS_RANGE_DISABLE BIT(4)
+#define STUSB160X_VDD_OVLO_DISABLE BIT(6)
+
+enum stusb160x_pwr_mode {
+ SOURCE_WITH_ACCESSORY,
+ SINK_WITH_ACCESSORY,
+ SINK_WITHOUT_ACCESSORY,
+ DUAL_WITH_ACCESSORY,
+ DUAL_WITH_ACCESSORY_AND_TRY_SRC,
+ DUAL_WITH_ACCESSORY_AND_TRY_SNK,
+};
+
+enum stusb160x_attached_mode {
+ NO_DEVICE_ATTACHED,
+ SINK_ATTACHED,
+ SOURCE_ATTACHED,
+ DEBUG_ACCESSORY_ATTACHED,
+ AUDIO_ACCESSORY_ATTACHED,
+};
+
+struct stusb160x {
+ struct device *dev;
+ struct regmap *regmap;
+ struct regulator *vdd_supply;
+ struct regulator *vsys_supply;
+ struct regulator *vconn_supply;
+ struct regulator *main_supply;
+
+ struct typec_port *port;
+ struct typec_capability capability;
+ struct typec_partner *partner;
+
+ enum typec_port_type port_type;
+ enum typec_pwr_opmode pwr_opmode;
+ bool vbus_on;
+
+ struct usb_role_switch *role_sw;
+};
+
+static bool stusb160x_reg_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case STUSB160X_ALERT_STATUS_MASK_CTRL:
+ case STUSB160X_CC_CAPABILITY_CTRL:
+ case STUSB160X_CC_VCONN_SWITCH_CTRL:
+ case STUSB160X_VCONN_MONITORING_CTRL:
+ case STUSB160X_VBUS_MONITORING_RANGE_CTRL:
+ case STUSB160X_RESET_CTRL:
+ case STUSB160X_VBUS_DISCHARGE_TIME_CTRL:
+ case STUSB160X_CC_POWER_MODE_CTRL:
+ case STUSB160X_VBUS_MONITORING_CTRL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool stusb160x_reg_readable(struct device *dev, unsigned int reg)
+{
+ if (reg <= 0x0A ||
+ (reg >= 0x14 && reg <= 0x17) ||
+ (reg >= 0x19 && reg <= 0x1D) ||
+ (reg >= 0x29 && reg <= 0x2D) ||
+ (reg == 0x1F || reg == 0x21 || reg == 0x24 || reg == 0x2F))
+ return false;
+ else
+ return true;
+}
+
+static bool stusb160x_reg_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case STUSB160X_ALERT_STATUS:
+ case STUSB160X_CC_CONNECTION_STATUS_TRANS:
+ case STUSB160X_CC_CONNECTION_STATUS:
+ case STUSB160X_MONITORING_STATUS_TRANS:
+ case STUSB160X_MONITORING_STATUS:
+ case STUSB160X_CC_OPERATION_STATUS:
+ case STUSB160X_HW_FAULT_STATUS_TRANS:
+ case STUSB160X_HW_FAULT_STATUS:
+ case STUSB160X_VBUS_DISCHARGE_STATUS:
+ case STUSB160X_VBUS_ENABLE_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool stusb160x_reg_precious(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case STUSB160X_ALERT_STATUS:
+ case STUSB160X_CC_CONNECTION_STATUS_TRANS:
+ case STUSB160X_MONITORING_STATUS_TRANS:
+ case STUSB160X_HW_FAULT_STATUS_TRANS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config stusb1600_regmap_config = {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .max_register = STUSB1600_REG_MAX,
+ .writeable_reg = stusb160x_reg_writeable,
+ .readable_reg = stusb160x_reg_readable,
+ .volatile_reg = stusb160x_reg_volatile,
+ .precious_reg = stusb160x_reg_precious,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static bool stusb160x_get_vconn(struct stusb160x *chip)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL, &val);
+ if (ret) {
+ dev_err(chip->dev, "Unable to get Vconn status: %d\n", ret);
+ return false;
+ }
+
+ return !!FIELD_GET(STUSB160X_CC_VCONN_SUPPLY_EN, val);
+}
+
+static int stusb160x_set_vconn(struct stusb160x *chip, bool on)
+{
+ int ret;
+
+ /* Manage VCONN input supply */
+ if (chip->vconn_supply) {
+ if (on) {
+ ret = regulator_enable(chip->vconn_supply);
+ if (ret) {
+ dev_err(chip->dev,
+ "failed to enable vconn supply: %d\n",
+ ret);
+ return ret;
+ }
+ } else {
+ regulator_disable(chip->vconn_supply);
+ }
+ }
+
+ /* Manage VCONN monitoring and power path */
+ ret = regmap_update_bits(chip->regmap, STUSB160X_VCONN_MONITORING_CTRL,
+ STUSB160X_VCONN_MONITORING_EN,
+ on ? STUSB160X_VCONN_MONITORING_EN : 0);
+ if (ret)
+ goto vconn_reg_disable;
+
+ return 0;
+
+vconn_reg_disable:
+ if (chip->vconn_supply && on)
+ regulator_disable(chip->vconn_supply);
+
+ return ret;
+}
+
+static enum typec_pwr_opmode stusb160x_get_pwr_opmode(struct stusb160x *chip)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL, &val);
+ if (ret) {
+ dev_err(chip->dev, "Unable to get pwr opmode: %d\n", ret);
+ return TYPEC_PWR_MODE_USB;
+ }
+
+ return FIELD_GET(STUSB160X_CC_CURRENT_ADVERTISED, val);
+}
+
+static enum typec_accessory stusb160x_get_accessory(u32 status)
+{
+ enum stusb160x_attached_mode mode;
+
+ mode = FIELD_GET(STUSB160X_CC_ATTACHED_MODE, status);
+
+ switch (mode) {
+ case DEBUG_ACCESSORY_ATTACHED:
+ return TYPEC_ACCESSORY_DEBUG;
+ case AUDIO_ACCESSORY_ATTACHED:
+ return TYPEC_ACCESSORY_AUDIO;
+ default:
+ return TYPEC_ACCESSORY_NONE;
+ }
+}
+
+static enum typec_role stusb160x_get_vconn_role(u32 status)
+{
+ if (FIELD_GET(STUSB160X_CC_VCONN_SUPPLY, status))
+ return TYPEC_SOURCE;
+
+ return TYPEC_SINK;
+}
+
+static void stusb160x_set_data_role(struct stusb160x *chip,
+ enum typec_data_role data_role,
+ bool attached)
+{
+ enum usb_role usb_role = USB_ROLE_NONE;
+
+ if (attached) {
+ if (data_role == TYPEC_HOST)
+ usb_role = USB_ROLE_HOST;
+ else
+ usb_role = USB_ROLE_DEVICE;
+ }
+
+ usb_role_switch_set_role(chip->role_sw, usb_role);
+ typec_set_data_role(chip->port, data_role);
+}
+
+static int stusb160x_attach(struct stusb160x *chip, u32 status)
+{
+ struct typec_partner_desc desc;
+ int ret;
+
+ if ((STUSB160X_CC_POWER_ROLE(status) == TYPEC_SOURCE) &&
+ chip->vdd_supply) {
+ ret = regulator_enable(chip->vdd_supply);
+ if (ret) {
+ dev_err(chip->dev,
+ "Failed to enable Vbus supply: %d\n", ret);
+ return ret;
+ }
+ chip->vbus_on = true;
+ }
+
+ desc.usb_pd = false;
+ desc.accessory = stusb160x_get_accessory(status);
+ desc.identity = NULL;
+
+ chip->partner = typec_register_partner(chip->port, &desc);
+ if (IS_ERR(chip->partner)) {
+ ret = PTR_ERR(chip->partner);
+ goto vbus_disable;
+ }
+
+ typec_set_pwr_role(chip->port, STUSB160X_CC_POWER_ROLE(status));
+ typec_set_pwr_opmode(chip->port, stusb160x_get_pwr_opmode(chip));
+ typec_set_vconn_role(chip->port, stusb160x_get_vconn_role(status));
+ stusb160x_set_data_role(chip, STUSB160X_CC_DATA_ROLE(status), true);
+
+ return 0;
+
+vbus_disable:
+ if (chip->vbus_on) {
+ regulator_disable(chip->vdd_supply);
+ chip->vbus_on = false;
+ }
+
+ return ret;
+}
+
+static void stusb160x_detach(struct stusb160x *chip, u32 status)
+{
+ typec_unregister_partner(chip->partner);
+ chip->partner = NULL;
+
+ typec_set_pwr_role(chip->port, STUSB160X_CC_POWER_ROLE(status));
+ typec_set_pwr_opmode(chip->port, TYPEC_PWR_MODE_USB);
+ typec_set_vconn_role(chip->port, stusb160x_get_vconn_role(status));
+ stusb160x_set_data_role(chip, STUSB160X_CC_DATA_ROLE(status), false);
+
+ if (chip->vbus_on) {
+ regulator_disable(chip->vdd_supply);
+ chip->vbus_on = false;
+ }
+}
+
+static irqreturn_t stusb160x_irq_handler(int irq, void *data)
+{
+ struct stusb160x *chip = data;
+ u32 pending, trans, status;
+ int ret;
+
+ ret = regmap_read(chip->regmap, STUSB160X_ALERT_STATUS, &pending);
+ if (ret)
+ goto err;
+
+ if (pending & STUSB160X_CC_CONNECTION) {
+ ret = regmap_read(chip->regmap,
+ STUSB160X_CC_CONNECTION_STATUS_TRANS, &trans);
+ if (ret)
+ goto err;
+ ret = regmap_read(chip->regmap,
+ STUSB160X_CC_CONNECTION_STATUS, &status);
+ if (ret)
+ goto err;
+
+ if (trans & STUSB160X_CC_ATTACH_TRANS) {
+ if (status & STUSB160X_CC_ATTACH) {
+ ret = stusb160x_attach(chip, status);
+ if (ret)
+ goto err;
+ } else {
+ stusb160x_detach(chip, status);
+ }
+ }
+ }
+err:
+ return IRQ_HANDLED;
+}
+
+static int stusb160x_irq_init(struct stusb160x *chip, int irq)
+{
+ u32 status;
+ int ret;
+
+ ret = regmap_read(chip->regmap,
+ STUSB160X_CC_CONNECTION_STATUS, &status);
+ if (ret)
+ return ret;
+
+ if (status & STUSB160X_CC_ATTACH) {
+ ret = stusb160x_attach(chip, status);
+ if (ret)
+ dev_err(chip->dev, "attach failed: %d\n", ret);
+ }
+
+ ret = devm_request_threaded_irq(chip->dev, irq, NULL,
+ stusb160x_irq_handler, IRQF_ONESHOT,
+ dev_name(chip->dev), chip);
+ if (ret)
+ goto partner_unregister;
+
+ /* Unmask CC_CONNECTION events */
+ ret = regmap_write_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL,
+ STUSB160X_CC_CONNECTION, 0);
+ if (ret)
+ goto partner_unregister;
+
+ return 0;
+
+partner_unregister:
+ if (chip->partner) {
+ typec_unregister_partner(chip->partner);
+ chip->partner = NULL;
+ }
+
+ return ret;
+}
+
+static int stusb160x_chip_init(struct stusb160x *chip)
+{
+ u32 val;
+ int ret;
+
+ /* Change the default Type-C power mode */
+ if (chip->port_type == TYPEC_PORT_SRC)
+ ret = regmap_update_bits(chip->regmap,
+ STUSB160X_CC_POWER_MODE_CTRL,
+ STUSB160X_CC_POWER_MODE,
+ SOURCE_WITH_ACCESSORY);
+ else if (chip->port_type == TYPEC_PORT_SNK)
+ ret = regmap_update_bits(chip->regmap,
+ STUSB160X_CC_POWER_MODE_CTRL,
+ STUSB160X_CC_POWER_MODE,
+ SINK_WITH_ACCESSORY);
+ else /* (chip->port_type == TYPEC_PORT_DRP) */
+ ret = regmap_update_bits(chip->regmap,
+ STUSB160X_CC_POWER_MODE_CTRL,
+ STUSB160X_CC_POWER_MODE,
+ DUAL_WITH_ACCESSORY);
+ if (ret)
+ return ret;
+
+ if (chip->port_type == TYPEC_PORT_SNK)
+ goto skip_src;
+
+ /* Change the default Type-C Source power operation mode capability */
+ ret = regmap_update_bits(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL,
+ STUSB160X_CC_CURRENT_ADVERTISED,
+ FIELD_PREP(STUSB160X_CC_CURRENT_ADVERTISED,
+ chip->pwr_opmode));
+ if (ret)
+ return ret;
+
+ /* Manage Type-C Source Vconn supply */
+ if (stusb160x_get_vconn(chip)) {
+ ret = stusb160x_set_vconn(chip, true);
+ if (ret)
+ return ret;
+ }
+
+skip_src:
+ /* Mask all events interrupts - to be unmasked with interrupt support */
+ ret = regmap_update_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL,
+ STUSB160X_ALL_ALERTS, STUSB160X_ALL_ALERTS);
+ if (ret)
+ return ret;
+
+ /* Read status at least once to clear any stale interrupts */
+ regmap_read(chip->regmap, STUSB160X_ALERT_STATUS, &val);
+ regmap_read(chip->regmap, STUSB160X_CC_CONNECTION_STATUS_TRANS, &val);
+ regmap_read(chip->regmap, STUSB160X_MONITORING_STATUS_TRANS, &val);
+ regmap_read(chip->regmap, STUSB160X_HW_FAULT_STATUS_TRANS, &val);
+
+ return 0;
+}
+
+static int stusb160x_get_fw_caps(struct stusb160x *chip,
+ struct fwnode_handle *fwnode)
+{
+ const char *cap_str;
+ int ret;
+
+ chip->capability.fwnode = fwnode;
+
+ /*
+ * Supported port type can be configured through device tree
+ * else it is read from chip registers in stusb160x_get_caps.
+ */
+ ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
+ if (!ret) {
+ chip->port_type = typec_find_port_power_role(cap_str);
+ if (chip->port_type < 0) {
+ ret = chip->port_type;
+ return ret;
+ }
+ }
+ chip->capability.type = chip->port_type;
+
+ /* Skip DRP/Source capabilities in case of Sink only */
+ if (chip->port_type == TYPEC_PORT_SNK)
+ return 0;
+
+ if (chip->port_type == TYPEC_PORT_DRP)
+ chip->capability.prefer_role = TYPEC_SINK;
+
+ /*
+ * Supported power operation mode can be configured through device tree
+ * else it is read from chip registers in stusb160x_get_caps.
+ */
+ ret = fwnode_property_read_string(fwnode, "power-opmode", &cap_str);
+ if (!ret) {
+ chip->pwr_opmode = typec_find_pwr_opmode(cap_str);
+ /* Power delivery not yet supported */
+ if (chip->pwr_opmode < 0 ||
+ chip->pwr_opmode == TYPEC_PWR_MODE_PD) {
+ ret = chip->pwr_opmode < 0 ? chip->pwr_opmode : -EINVAL;
+ dev_err(chip->dev, "bad power operation mode: %d\n",
+ chip->pwr_opmode);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int stusb160x_get_caps(struct stusb160x *chip)
+{
+ enum typec_port_type *type = &chip->capability.type;
+ enum typec_port_data *data = &chip->capability.data;
+ enum typec_accessory *accessory = chip->capability.accessory;
+ u32 val;
+ int ret;
+
+ chip->capability.revision = USB_TYPEC_REV_1_2;
+
+ ret = regmap_read(chip->regmap, STUSB160X_CC_POWER_MODE_CTRL, &val);
+ if (ret)
+ return ret;
+
+ switch (FIELD_GET(STUSB160X_CC_POWER_MODE, val)) {
+ case SOURCE_WITH_ACCESSORY:
+ *type = TYPEC_PORT_SRC;
+ *data = TYPEC_PORT_DFP;
+ *accessory++ = TYPEC_ACCESSORY_AUDIO;
+ *accessory++ = TYPEC_ACCESSORY_DEBUG;
+ break;
+ case SINK_WITH_ACCESSORY:
+ *type = TYPEC_PORT_SNK;
+ *data = TYPEC_PORT_UFP;
+ *accessory++ = TYPEC_ACCESSORY_AUDIO;
+ *accessory++ = TYPEC_ACCESSORY_DEBUG;
+ break;
+ case SINK_WITHOUT_ACCESSORY:
+ *type = TYPEC_PORT_SNK;
+ *data = TYPEC_PORT_UFP;
+ break;
+ case DUAL_WITH_ACCESSORY:
+ case DUAL_WITH_ACCESSORY_AND_TRY_SRC:
+ case DUAL_WITH_ACCESSORY_AND_TRY_SNK:
+ *type = TYPEC_PORT_DRP;
+ *data = TYPEC_PORT_DRD;
+ *accessory++ = TYPEC_ACCESSORY_AUDIO;
+ *accessory++ = TYPEC_ACCESSORY_DEBUG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ chip->port_type = *type;
+ chip->pwr_opmode = stusb160x_get_pwr_opmode(chip);
+
+ return 0;
+}
+
+static const struct of_device_id stusb160x_of_match[] = {
+ { .compatible = "st,stusb1600", .data = &stusb1600_regmap_config},
+ {},
+};
+
+static int stusb160x_probe(struct i2c_client *client)
+{
+ struct stusb160x *chip;
+ const struct of_device_id *match;
+ struct regmap_config *regmap_config;
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ chip = devm_kzalloc(&client->dev, sizeof(struct stusb160x), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, chip);
+
+ match = i2c_of_match_device(stusb160x_of_match, client);
+ regmap_config = (struct regmap_config *)match->data;
+ chip->regmap = devm_regmap_init_i2c(client, regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ dev_err(&client->dev,
+ "Failed to allocate register map:%d\n", ret);
+ return ret;
+ }
+
+ chip->dev = &client->dev;
+
+ chip->vsys_supply = devm_regulator_get_optional(chip->dev, "vsys");
+ if (IS_ERR(chip->vsys_supply)) {
+ ret = PTR_ERR(chip->vsys_supply);
+ if (ret != -ENODEV)
+ return ret;
+ chip->vsys_supply = NULL;
+ }
+
+ chip->vdd_supply = devm_regulator_get_optional(chip->dev, "vdd");
+ if (IS_ERR(chip->vdd_supply)) {
+ ret = PTR_ERR(chip->vdd_supply);
+ if (ret != -ENODEV)
+ return ret;
+ chip->vdd_supply = NULL;
+ }
+
+ chip->vconn_supply = devm_regulator_get_optional(chip->dev, "vconn");
+ if (IS_ERR(chip->vconn_supply)) {
+ ret = PTR_ERR(chip->vconn_supply);
+ if (ret != -ENODEV)
+ return ret;
+ chip->vconn_supply = NULL;
+ }
+
+ fwnode = device_get_named_child_node(chip->dev, "connector");
+ if (IS_ERR(fwnode))
+ return PTR_ERR(fwnode);
+
+ /*
+ * When both VDD and VSYS power supplies are present, the low power
+ * supply VSYS is selected when VSYS voltage is above 3.1 V.
+ * Otherwise VDD is selected.
+ */
+ if (chip->vdd_supply &&
+ (!chip->vsys_supply ||
+ (regulator_get_voltage(chip->vsys_supply) <= 3100000)))
+ chip->main_supply = chip->vdd_supply;
+ else
+ chip->main_supply = chip->vsys_supply;
+
+ if (chip->main_supply) {
+ ret = regulator_enable(chip->main_supply);
+ if (ret) {
+ dev_err(chip->dev,
+ "Failed to enable main supply: %d\n", ret);
+ goto fwnode_put;
+ }
+ }
+
+ /* Get configuration from chip */
+ ret = stusb160x_get_caps(chip);
+ if (ret) {
+ dev_err(chip->dev, "Failed to get port caps: %d\n", ret);
+ goto main_reg_disable;
+ }
+
+ /* Get optional re-configuration from device tree */
+ ret = stusb160x_get_fw_caps(chip, fwnode);
+ if (ret) {
+ dev_err(chip->dev, "Failed to get connector caps: %d\n", ret);
+ goto main_reg_disable;
+ }
+
+ ret = stusb160x_chip_init(chip);
+ if (ret) {
+ dev_err(chip->dev, "Failed to init port: %d\n", ret);
+ goto main_reg_disable;
+ }
+
+ chip->port = typec_register_port(chip->dev, &chip->capability);
+ if (!chip->port) {
+ ret = -ENODEV;
+ goto all_reg_disable;
+ }
+
+ /*
+ * Default power operation mode initialization: will be updated upon
+ * attach/detach interrupt
+ */
+ typec_set_pwr_opmode(chip->port, chip->pwr_opmode);
+
+ if (client->irq) {
+ ret = stusb160x_irq_init(chip, client->irq);
+ if (ret)
+ goto port_unregister;
+
+ chip->role_sw = fwnode_usb_role_switch_get(fwnode);
+ if (IS_ERR(chip->role_sw)) {
+ ret = PTR_ERR(chip->role_sw);
+ if (ret != -EPROBE_DEFER)
+ dev_err(chip->dev,
+ "Failed to get usb role switch: %d\n",
+ ret);
+ goto port_unregister;
+ }
+ } else {
+ /*
+ * If Source or Dual power role, need to enable VDD supply
+ * providing Vbus if present. In case of interrupt support,
+ * VDD supply will be dynamically managed upon attach/detach
+ * interrupt.
+ */
+ if (chip->port_type != TYPEC_PORT_SNK && chip->vdd_supply) {
+ ret = regulator_enable(chip->vdd_supply);
+ if (ret) {
+ dev_err(chip->dev,
+ "Failed to enable VDD supply: %d\n",
+ ret);
+ goto port_unregister;
+ }
+ chip->vbus_on = true;
+ }
+ }
+
+ fwnode_handle_put(fwnode);
+
+ return 0;
+
+port_unregister:
+ typec_unregister_port(chip->port);
+all_reg_disable:
+ if (stusb160x_get_vconn(chip))
+ stusb160x_set_vconn(chip, false);
+main_reg_disable:
+ if (chip->main_supply)
+ regulator_disable(chip->main_supply);
+fwnode_put:
+ fwnode_handle_put(fwnode);
+
+ return ret;
+}
+
+static int stusb160x_remove(struct i2c_client *client)
+{
+ struct stusb160x *chip = i2c_get_clientdata(client);
+
+ if (chip->partner) {
+ typec_unregister_partner(chip->partner);
+ chip->partner = NULL;
+ }
+
+ if (chip->vbus_on)
+ regulator_disable(chip->vdd_supply);
+
+ if (chip->role_sw)
+ usb_role_switch_put(chip->role_sw);
+
+ typec_unregister_port(chip->port);
+
+ if (stusb160x_get_vconn(chip))
+ stusb160x_set_vconn(chip, false);
+
+ if (chip->main_supply)
+ regulator_disable(chip->main_supply);
+
+ return 0;
+}
+
+static int __maybe_unused stusb160x_suspend(struct device *dev)
+{
+ struct stusb160x *chip = dev_get_drvdata(dev);
+
+ /* Mask interrupts */
+ return regmap_update_bits(chip->regmap,
+ STUSB160X_ALERT_STATUS_MASK_CTRL,
+ STUSB160X_ALL_ALERTS, STUSB160X_ALL_ALERTS);
+}
+
+static int __maybe_unused stusb160x_resume(struct device *dev)
+{
+ struct stusb160x *chip = dev_get_drvdata(dev);
+ u32 status;
+ int ret;
+
+ ret = regcache_sync(chip->regmap);
+ if (ret)
+ return ret;
+
+ /* Check if attach/detach occurred during low power */
+ ret = regmap_read(chip->regmap,
+ STUSB160X_CC_CONNECTION_STATUS, &status);
+ if (ret)
+ return ret;
+
+ if (chip->partner && !(status & STUSB160X_CC_ATTACH))
+ stusb160x_detach(chip, status);
+
+ if (!chip->partner && (status & STUSB160X_CC_ATTACH)) {
+ ret = stusb160x_attach(chip, status);
+ if (ret)
+ dev_err(chip->dev, "attach failed: %d\n", ret);
+ }
+
+ /* Unmask interrupts */
+ return regmap_write_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL,
+ STUSB160X_CC_CONNECTION, 0);
+}
+
+static SIMPLE_DEV_PM_OPS(stusb160x_pm_ops, stusb160x_suspend, stusb160x_resume);
+
+static struct i2c_driver stusb160x_driver = {
+ .driver = {
+ .name = "stusb160x",
+ .pm = &stusb160x_pm_ops,
+ .of_match_table = stusb160x_of_match,
+ },
+ .probe_new = stusb160x_probe,
+ .remove = stusb160x_remove,
+};
+module_i2c_driver(stusb160x_driver);
+
+MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics STUSB160x Type-C controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig
index fa3f39336246..557f392fe24d 100644
--- a/drivers/usb/typec/tcpm/Kconfig
+++ b/drivers/usb/typec/tcpm/Kconfig
@@ -27,6 +27,20 @@ config TYPEC_RT1711H
Type-C Port Controller Manager to provide USB PD and USB
Type-C functionalities.
+config TYPEC_MT6360
+ tristate "Mediatek MT6360 Type-C driver"
+ depends on MFD_MT6360
+ help
+ Mediatek MT6360 is a multi-functional IC that includes
+ USB Type-C. It works with Type-C Port Controller Manager
+ to provide USB PD and USB Type-C functionalities.
+
+config TYPEC_TCPCI_MAXIM
+ tristate "Maxim TCPCI based Type-C chip driver"
+ help
+ MAXIM TCPCI based Type-C/PD chip driver. Works with
+ with Type-C Port Controller Manager.
+
endif # TYPEC_TCPCI
config TYPEC_FUSB302
diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile
index a5ff6c8eb892..7d499f3569fd 100644
--- a/drivers/usb/typec/tcpm/Makefile
+++ b/drivers/usb/typec/tcpm/Makefile
@@ -1,7 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_TYPEC_TCPM) += tcpm.o
-obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o
-obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
-typec_wcove-y := wcove.o
-obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o
-obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o
+obj-$(CONFIG_TYPEC_TCPM) += tcpm.o
+obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o
+obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
+typec_wcove-y := wcove.o
+obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o
+obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o
+obj-$(CONFIG_TYPEC_MT6360) += tcpci_mt6360.o
+obj-$(CONFIG_TYPEC_TCPCI_MAXIM) += tcpci_maxim.o
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index bd80e03b2b6f..f9f0af64da5f 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -38,6 +38,12 @@ struct tcpci_chip {
struct tcpci_data data;
};
+struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci)
+{
+ return tcpci->port;
+}
+EXPORT_SYMBOL_GPL(tcpci_get_tcpm_port);
+
static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc)
{
return container_of(tcpc, struct tcpci, tcpc);
@@ -191,12 +197,47 @@ static int tcpci_set_polarity(struct tcpc_dev *tcpc,
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
unsigned int reg;
int ret;
+ enum typec_cc_status cc1, cc2;
- /* Keep the disconnect cc line open */
+ /* Obtain Rp setting from role control */
ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &reg);
if (ret < 0)
return ret;
+ ret = tcpci_get_cc(tcpc, &cc1, &cc2);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * When port has drp toggling enabled, ROLE_CONTROL would only have the initial
+ * terminations for the toggling and does not indicate the final cc
+ * terminations when ConnectionResult is 0 i.e. drp toggling stops and
+ * the connection is resolbed. Infer port role from TCPC_CC_STATUS based on the
+ * terminations seen. The port role is then used to set the cc terminations.
+ */
+ if (reg & TCPC_ROLE_CTRL_DRP) {
+ /* Disable DRP for the OPEN setting to take effect */
+ reg = reg & ~TCPC_ROLE_CTRL_DRP;
+
+ if (polarity == TYPEC_POLARITY_CC2) {
+ reg &= ~(TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT);
+ /* Local port is source */
+ if (cc2 == TYPEC_CC_RD)
+ /* Role control would have the Rp setting when DRP was enabled */
+ reg |= TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT;
+ else
+ reg |= TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT;
+ } else {
+ reg &= ~(TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT);
+ /* Local port is source */
+ if (cc1 == TYPEC_CC_RD)
+ /* Role control would have the Rp setting when DRP was enabled */
+ reg |= TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT;
+ else
+ reg |= TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT;
+ }
+ }
+
if (polarity == TYPEC_POLARITY_CC2)
reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT;
else
@@ -227,6 +268,22 @@ static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable)
enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
}
+static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable)
+{
+ struct tcpci *tcpci = tcpc_to_tcpci(dev);
+ int ret;
+
+ /* To prevent disconnect during FRS, set disconnect threshold to 3.5V */
+ ret = tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, enable ? 0 : 0x8c);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_FAST_ROLE_SWAP_EN, enable ?
+ TCPC_FAST_ROLE_SWAP_EN : 0);
+
+ return ret;
+}
+
static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable)
{
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
@@ -287,6 +344,13 @@ static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
int ret;
+ if (tcpci->data->set_vbus) {
+ ret = tcpci->data->set_vbus(tcpci, tcpci->data, source, sink);
+ /* Bypass when ret > 0 */
+ if (ret != 0)
+ return ret < 0 ? ret : 0;
+ }
+
/* Disable both source and sink first before enabling anything */
if (!source) {
@@ -330,23 +394,47 @@ static int tcpci_pd_transmit(struct tcpc_dev *tcpc,
int ret;
cnt = msg ? pd_header_cnt(header) * 4 : 0;
- ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2);
- if (ret < 0)
- return ret;
+ /**
+ * TCPCI spec forbids direct access of TCPC_TX_DATA.
+ * But, since some of the chipsets offer this capability,
+ * it's fair to support both.
+ */
+ if (tcpci->data->TX_BUF_BYTE_x_hidden) {
+ u8 buf[TCPC_TRANSMIT_BUFFER_MAX_LEN] = {0,};
+ u8 pos = 0;
- ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
- if (ret < 0)
- return ret;
+ /* Payload + header + TCPC_TX_BYTE_CNT */
+ buf[pos++] = cnt + 2;
+
+ if (msg)
+ memcpy(&buf[pos], &msg->header, sizeof(msg->header));
- if (cnt > 0) {
- ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA,
- &msg->payload, cnt);
+ pos += sizeof(header);
+
+ if (cnt > 0)
+ memcpy(&buf[pos], msg->payload, cnt);
+
+ pos += cnt;
+ ret = regmap_raw_write(tcpci->regmap, TCPC_TX_BYTE_CNT, buf, pos);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2);
if (ret < 0)
return ret;
+
+ ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
+ if (ret < 0)
+ return ret;
+
+ if (cnt > 0) {
+ ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, &msg->payload, cnt);
+ if (ret < 0)
+ return ret;
+ }
}
- reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) |
- (type << TCPC_TRANSMIT_TYPE_SHIFT);
+ reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) | (type << TCPC_TRANSMIT_TYPE_SHIFT);
ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
if (ret < 0)
return ret;
@@ -539,6 +627,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
tcpci->tcpc.set_roles = tcpci_set_roles;
tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
tcpci->tcpc.set_bist_data = tcpci_set_bist_data;
+ tcpci->tcpc.enable_frs = tcpci_enable_frs;
err = tcpci_parse_config(tcpci);
if (err < 0)
diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h
index 11c36d086c86..5ef07a56d67a 100644
--- a/drivers/usb/typec/tcpm/tcpci.h
+++ b/drivers/usb/typec/tcpm/tcpci.h
@@ -16,6 +16,8 @@
#define TCPC_PD_INT_REV 0xa
#define TCPC_ALERT 0x10
+#define TCPC_ALERT_EXTND BIT(14)
+#define TCPC_ALERT_EXTENDED_STATUS BIT(13)
#define TCPC_ALERT_VBUS_DISCNCT BIT(11)
#define TCPC_ALERT_RX_BUF_OVF BIT(10)
#define TCPC_ALERT_FAULT BIT(9)
@@ -32,6 +34,13 @@
#define TCPC_ALERT_MASK 0x12
#define TCPC_POWER_STATUS_MASK 0x14
#define TCPC_FAULT_STATUS_MASK 0x15
+
+#define TCPC_EXTENDED_STATUS_MASK 0x16
+#define TCPC_EXTENDED_STATUS_MASK_VSAFE0V BIT(0)
+
+#define TCPC_ALERT_EXTENDED_MASK 0x17
+#define TCPC_SINK_FAST_ROLE_SWAP BIT(0)
+
#define TCPC_CONFIG_STD_OUTPUT 0x18
#define TCPC_TCPC_CTRL 0x19
@@ -58,6 +67,7 @@
#define TCPC_POWER_CTRL 0x1c
#define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0)
+#define TCPC_FAST_ROLE_SWAP_EN BIT(7)
#define TCPC_CC_STATUS 0x1d
#define TCPC_CC_STATUS_TOGGLING BIT(5)
@@ -69,11 +79,14 @@
#define TCPC_POWER_STATUS 0x1e
#define TCPC_POWER_STATUS_UNINIT BIT(6)
+#define TCPC_POWER_STATUS_SOURCING_VBUS BIT(4)
#define TCPC_POWER_STATUS_VBUS_DET BIT(3)
#define TCPC_POWER_STATUS_VBUS_PRES BIT(2)
#define TCPC_FAULT_STATUS 0x1f
+#define TCPC_ALERT_EXTENDED 0x21
+
#define TCPC_COMMAND 0x23
#define TCPC_CMD_WAKE_I2C 0x11
#define TCPC_CMD_DISABLE_VBUS_DETECT 0x22
@@ -104,6 +117,7 @@
#define TCPC_RX_BYTE_CNT 0x30
#define TCPC_RX_BUF_FRAME_TYPE 0x31
+#define TCPC_RX_BUF_FRAME_TYPE_SOP 0
#define TCPC_RX_HDR 0x32
#define TCPC_RX_DATA 0x34 /* through 0x4f */
@@ -123,18 +137,29 @@
#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76
#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78
+/* I2C_WRITE_BYTE_COUNT + 1 when TX_BUF_BYTE_x is only accessible I2C_WRITE_BYTE_COUNT */
+#define TCPC_TRANSMIT_BUFFER_MAX_LEN 31
+
+/*
+ * @TX_BUF_BYTE_x_hidden
+ * optional; Set when TX_BUF_BYTE_x can only be accessed through I2C_WRITE_BYTE_COUNT.
+ */
struct tcpci;
struct tcpci_data {
struct regmap *regmap;
+ unsigned char TX_BUF_BYTE_x_hidden:1;
int (*init)(struct tcpci *tcpci, struct tcpci_data *data);
int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data,
bool enable);
int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data,
enum typec_cc_status cc);
+ int (*set_vbus)(struct tcpci *tcpci, struct tcpci_data *data, bool source, bool sink);
};
struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data);
void tcpci_unregister_port(struct tcpci *tcpci);
irqreturn_t tcpci_irq(struct tcpci *tcpci);
+struct tcpm_port;
+struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci);
#endif /* __LINUX_USB_TCPCI_H */
diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c
new file mode 100644
index 000000000000..723d7dd38f75
--- /dev/null
+++ b/drivers/usb/typec/tcpm/tcpci_maxim.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Google LLC
+ *
+ * MAXIM TCPCI based TCPC driver
+ */
+
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/tcpm.h>
+#include <linux/usb/typec.h>
+
+#include "tcpci.h"
+
+#define PD_ACTIVITY_TIMEOUT_MS 10000
+
+#define TCPC_VENDOR_ALERT 0x80
+
+#define TCPC_RECEIVE_BUFFER_COUNT_OFFSET 0
+#define TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET 1
+#define TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET 2
+
+/*
+ * LongMessage not supported, hence 32 bytes for buf to be read from RECEIVE_BUFFER.
+ * DEVICE_CAPABILITIES_2.LongMessage = 0, the value in READABLE_BYTE_COUNT reg shall be
+ * less than or equal to 31. Since, RECEIVE_BUFFER len = 31 + 1(READABLE_BYTE_COUNT).
+ */
+#define TCPC_RECEIVE_BUFFER_LEN 32
+
+#define MAX_BUCK_BOOST_SID 0x69
+#define MAX_BUCK_BOOST_OP 0xb9
+#define MAX_BUCK_BOOST_OFF 0
+#define MAX_BUCK_BOOST_SOURCE 0xa
+#define MAX_BUCK_BOOST_SINK 0x5
+
+struct max_tcpci_chip {
+ struct tcpci_data data;
+ struct tcpci *tcpci;
+ struct device *dev;
+ struct i2c_client *client;
+ struct tcpm_port *port;
+};
+
+static const struct regmap_range max_tcpci_tcpci_range[] = {
+ regmap_reg_range(0x00, 0x95)
+};
+
+const struct regmap_access_table max_tcpci_tcpci_write_table = {
+ .yes_ranges = max_tcpci_tcpci_range,
+ .n_yes_ranges = ARRAY_SIZE(max_tcpci_tcpci_range),
+};
+
+static const struct regmap_config max_tcpci_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x95,
+ .wr_table = &max_tcpci_tcpci_write_table,
+};
+
+static struct max_tcpci_chip *tdata_to_max_tcpci(struct tcpci_data *tdata)
+{
+ return container_of(tdata, struct max_tcpci_chip, data);
+}
+
+static int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val)
+{
+ return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16));
+}
+
+static int max_tcpci_write16(struct max_tcpci_chip *chip, unsigned int reg, u16 val)
+{
+ return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16));
+}
+
+static int max_tcpci_read8(struct max_tcpci_chip *chip, unsigned int reg, u8 *val)
+{
+ return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8));
+}
+
+static int max_tcpci_write8(struct max_tcpci_chip *chip, unsigned int reg, u8 val)
+{
+ return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8));
+}
+
+static void max_tcpci_init_regs(struct max_tcpci_chip *chip)
+{
+ u16 alert_mask = 0;
+ int ret;
+
+ ret = max_tcpci_write16(chip, TCPC_ALERT, 0xffff);
+ if (ret < 0) {
+ dev_err(chip->dev, "Error writing to TCPC_ALERT ret:%d\n", ret);
+ return;
+ }
+
+ ret = max_tcpci_write16(chip, TCPC_VENDOR_ALERT, 0xffff);
+ if (ret < 0) {
+ dev_err(chip->dev, "Error writing to TCPC_VENDOR_ALERT ret:%d\n", ret);
+ return;
+ }
+
+ ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, 0xff);
+ if (ret < 0) {
+ dev_err(chip->dev, "Unable to clear TCPC_ALERT_EXTENDED ret:%d\n", ret);
+ return;
+ }
+
+ alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_FAILED |
+ TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_RX_STATUS | TCPC_ALERT_CC_STATUS |
+ TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_POWER_STATUS |
+ /* Enable Extended alert for detecting Fast Role Swap Signal */
+ TCPC_ALERT_EXTND;
+
+ ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask);
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "Error enabling TCPC_ALERT: TCPC_ALERT_MASK write failed ret:%d\n", ret);
+ return;
+ }
+
+ /* Enable vbus voltage monitoring and voltage alerts */
+ ret = max_tcpci_write8(chip, TCPC_POWER_CTRL, 0);
+ if (ret < 0) {
+ dev_err(chip->dev, "Error writing to TCPC_POWER_CTRL ret:%d\n", ret);
+ return;
+ }
+
+ ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED_MASK, TCPC_SINK_FAST_ROLE_SWAP);
+ if (ret < 0)
+ return;
+}
+
+static void process_rx(struct max_tcpci_chip *chip, u16 status)
+{
+ struct pd_message msg;
+ u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN];
+ int ret, payload_index;
+ u8 *rx_buf_ptr;
+
+ /*
+ * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers
+ * plus one (for the RX_BUF_FRAME_TYPE) Table 4-36.
+ * Read the count and frame type.
+ */
+ ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, 2);
+ if (ret < 0) {
+ dev_err(chip->dev, "TCPC_RX_BYTE_CNT read failed ret:%d", ret);
+ return;
+ }
+
+ count = rx_buf[TCPC_RECEIVE_BUFFER_COUNT_OFFSET];
+ frame_type = rx_buf[TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET];
+
+ if (count == 0 || frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP) {
+ max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
+ dev_err(chip->dev, "%s", count == 0 ? "error: count is 0" :
+ "error frame_type is not SOP");
+ return;
+ }
+
+ if (count > sizeof(struct pd_message) || count + 1 > TCPC_RECEIVE_BUFFER_LEN) {
+ dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d", count);
+ return;
+ }
+
+ /*
+ * Read count + 1 as RX_BUF_BYTE_x is hidden and can only be read through
+ * TCPC_RX_BYTE_CNT
+ */
+ count += 1;
+ ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, count);
+ if (ret < 0) {
+ dev_err(chip->dev, "Error: TCPC_RX_BYTE_CNT read failed: %d", ret);
+ return;
+ }
+
+ rx_buf_ptr = rx_buf + TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET;
+ msg.header = cpu_to_le16(*(u16 *)rx_buf_ptr);
+ rx_buf_ptr = rx_buf_ptr + sizeof(msg.header);
+ for (payload_index = 0; payload_index < pd_header_cnt_le(msg.header); payload_index++,
+ rx_buf_ptr += sizeof(msg.payload[0]))
+ msg.payload[payload_index] = cpu_to_le32(*(u32 *)rx_buf_ptr);
+
+ /*
+ * Read complete, clear RX status alert bit.
+ * Clear overflow as well if set.
+ */
+ ret = max_tcpci_write16(chip, TCPC_ALERT, status & TCPC_ALERT_RX_BUF_OVF ?
+ TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_BUF_OVF :
+ TCPC_ALERT_RX_STATUS);
+ if (ret < 0)
+ return;
+
+ tcpm_pd_receive(chip->port, &msg);
+}
+
+static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink)
+{
+ struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata);
+ u8 buffer_source[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SOURCE};
+ u8 buffer_sink[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SINK};
+ u8 buffer_none[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_OFF};
+ struct i2c_client *i2c = chip->client;
+ int ret;
+
+ struct i2c_msg msgs[] = {
+ {
+ .addr = MAX_BUCK_BOOST_SID,
+ .flags = i2c->flags & I2C_M_TEN,
+ .len = 2,
+ .buf = source ? buffer_source : sink ? buffer_sink : buffer_none,
+ },
+ };
+
+ if (source && sink) {
+ dev_err(chip->dev, "Both source and sink set\n");
+ return -EINVAL;
+ }
+
+ ret = i2c_transfer(i2c->adapter, msgs, 1);
+
+ return ret < 0 ? ret : 1;
+}
+
+static void process_power_status(struct max_tcpci_chip *chip)
+{
+ u8 pwr_status;
+ int ret;
+
+ ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &pwr_status);
+ if (ret < 0)
+ return;
+
+ if (pwr_status == 0xff) {
+ max_tcpci_init_regs(chip);
+ } else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS) {
+ tcpm_sourcing_vbus(chip->port);
+ /*
+ * Alawys re-enable boost here.
+ * In normal case, when say an headset is attached, TCPM would
+ * have instructed to TCPC to enable boost, so the call is a
+ * no-op.
+ * But for Fast Role Swap case, Boost turns on autonomously without
+ * AP intervention, but, needs AP to enable source mode explicitly
+ * for AP to regain control.
+ */
+ max_tcpci_set_vbus(chip->tcpci, &chip->data, true, false);
+ } else {
+ tcpm_vbus_change(chip->port);
+ }
+}
+
+static void process_tx(struct max_tcpci_chip *chip, u16 status)
+{
+ if (status & TCPC_ALERT_TX_SUCCESS)
+ tcpm_pd_transmit_complete(chip->port, TCPC_TX_SUCCESS);
+ else if (status & TCPC_ALERT_TX_DISCARDED)
+ tcpm_pd_transmit_complete(chip->port, TCPC_TX_DISCARDED);
+ else if (status & TCPC_ALERT_TX_FAILED)
+ tcpm_pd_transmit_complete(chip->port, TCPC_TX_FAILED);
+
+ /* Reinit regs as Hard reset sets them to default value */
+ if ((status & TCPC_ALERT_TX_SUCCESS) && (status & TCPC_ALERT_TX_FAILED))
+ max_tcpci_init_regs(chip);
+}
+
+static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status)
+{
+ u16 mask;
+ int ret;
+ u8 reg_status;
+
+ /*
+ * Clear alert status for everything except RX_STATUS, which shouldn't
+ * be cleared until we have successfully retrieved message.
+ */
+ if (status & ~TCPC_ALERT_RX_STATUS) {
+ mask = status & TCPC_ALERT_RX_BUF_OVF ?
+ status & ~(TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_BUF_OVF) :
+ status & ~TCPC_ALERT_RX_STATUS;
+ ret = max_tcpci_write16(chip, TCPC_ALERT, mask);
+ if (ret < 0) {
+ dev_err(chip->dev, "ALERT clear failed\n");
+ return ret;
+ }
+ }
+
+ if (status & TCPC_ALERT_RX_BUF_OVF && !(status & TCPC_ALERT_RX_STATUS)) {
+ ret = max_tcpci_write16(chip, TCPC_ALERT, (TCPC_ALERT_RX_STATUS |
+ TCPC_ALERT_RX_BUF_OVF));
+ if (ret < 0) {
+ dev_err(chip->dev, "ALERT clear failed\n");
+ return ret;
+ }
+ }
+
+ if (status & TCPC_ALERT_EXTND) {
+ ret = max_tcpci_read8(chip, TCPC_ALERT_EXTENDED, &reg_status);
+ if (ret < 0)
+ return ret;
+
+ ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, reg_status);
+ if (ret < 0)
+ return ret;
+
+ if (reg_status & TCPC_SINK_FAST_ROLE_SWAP) {
+ dev_info(chip->dev, "FRS Signal");
+ tcpm_sink_frs(chip->port);
+ }
+ }
+
+ if (status & TCPC_ALERT_RX_STATUS)
+ process_rx(chip, status);
+
+ if (status & TCPC_ALERT_VBUS_DISCNCT)
+ tcpm_vbus_change(chip->port);
+
+ if (status & TCPC_ALERT_CC_STATUS)
+ tcpm_cc_change(chip->port);
+
+ if (status & TCPC_ALERT_POWER_STATUS)
+ process_power_status(chip);
+
+ if (status & TCPC_ALERT_RX_HARD_RST) {
+ tcpm_pd_hard_reset(chip->port);
+ max_tcpci_init_regs(chip);
+ }
+
+ if (status & TCPC_ALERT_TX_SUCCESS || status & TCPC_ALERT_TX_DISCARDED || status &
+ TCPC_ALERT_TX_FAILED)
+ process_tx(chip, status);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max_tcpci_irq(int irq, void *dev_id)
+{
+ struct max_tcpci_chip *chip = dev_id;
+ u16 status;
+ irqreturn_t irq_return;
+ int ret;
+
+ if (!chip->port)
+ return IRQ_HANDLED;
+
+ ret = max_tcpci_read16(chip, TCPC_ALERT, &status);
+ if (ret < 0) {
+ dev_err(chip->dev, "ALERT read failed\n");
+ return ret;
+ }
+ while (status) {
+ irq_return = _max_tcpci_irq(chip, status);
+ /* Do not return if the ALERT is already set. */
+ ret = max_tcpci_read16(chip, TCPC_ALERT, &status);
+ if (ret < 0)
+ break;
+ }
+
+ return irq_return;
+}
+
+static irqreturn_t max_tcpci_isr(int irq, void *dev_id)
+{
+ struct max_tcpci_chip *chip = dev_id;
+
+ pm_wakeup_event(chip->dev, PD_ACTIVITY_TIMEOUT_MS);
+
+ if (!chip->port)
+ return IRQ_HANDLED;
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int max_tcpci_init_alert(struct max_tcpci_chip *chip, struct i2c_client *client)
+{
+ int ret;
+
+ ret = devm_request_threaded_irq(chip->dev, client->irq, max_tcpci_isr, max_tcpci_irq,
+ (IRQF_TRIGGER_LOW | IRQF_ONESHOT), dev_name(chip->dev),
+ chip);
+
+ if (ret < 0)
+ return ret;
+
+ enable_irq_wake(client->irq);
+ return 0;
+}
+
+static int max_tcpci_start_toggling(struct tcpci *tcpci, struct tcpci_data *tdata,
+ enum typec_cc_status cc)
+{
+ struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata);
+
+ max_tcpci_init_regs(chip);
+
+ return 0;
+}
+
+static int tcpci_init(struct tcpci *tcpci, struct tcpci_data *data)
+{
+ /*
+ * Generic TCPCI overwrites the regs once this driver initializes
+ * them. Prevent this by returning -1.
+ */
+ return -1;
+}
+
+static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id *i2c_id)
+{
+ int ret;
+ struct max_tcpci_chip *chip;
+ u8 power_status;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->data.regmap = devm_regmap_init_i2c(client, &max_tcpci_regmap_config);
+ if (IS_ERR(chip->data.regmap)) {
+ dev_err(&client->dev, "Regmap init failed\n");
+ return PTR_ERR(chip->data.regmap);
+ }
+
+ chip->dev = &client->dev;
+ i2c_set_clientdata(client, chip);
+
+ ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &power_status);
+ if (ret < 0)
+ return ret;
+
+ /* Chip level tcpci callbacks */
+ chip->data.set_vbus = max_tcpci_set_vbus;
+ chip->data.start_drp_toggling = max_tcpci_start_toggling;
+ chip->data.TX_BUF_BYTE_x_hidden = true;
+ chip->data.init = tcpci_init;
+
+ max_tcpci_init_regs(chip);
+ chip->tcpci = tcpci_register_port(chip->dev, &chip->data);
+ if (IS_ERR_OR_NULL(chip->tcpci)) {
+ dev_err(&client->dev, "TCPCI port registration failed");
+ ret = PTR_ERR(chip->tcpci);
+ return PTR_ERR(chip->tcpci);
+ }
+ chip->port = tcpci_get_tcpm_port(chip->tcpci);
+ ret = max_tcpci_init_alert(chip, client);
+ if (ret < 0)
+ goto unreg_port;
+
+ device_init_wakeup(chip->dev, true);
+ return 0;
+
+unreg_port:
+ tcpci_unregister_port(chip->tcpci);
+
+ return ret;
+}
+
+static int max_tcpci_remove(struct i2c_client *client)
+{
+ struct max_tcpci_chip *chip = i2c_get_clientdata(client);
+
+ if (!IS_ERR_OR_NULL(chip->tcpci))
+ tcpci_unregister_port(chip->tcpci);
+
+ return 0;
+}
+
+static const struct i2c_device_id max_tcpci_id[] = {
+ { "maxtcpc", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max_tcpci_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id max_tcpci_of_match[] = {
+ { .compatible = "maxim,tcpc", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, max_tcpci_of_match);
+#endif
+
+static struct i2c_driver max_tcpci_i2c_driver = {
+ .driver = {
+ .name = "maxtcpc",
+ .of_match_table = of_match_ptr(max_tcpci_of_match),
+ },
+ .probe = max_tcpci_probe,
+ .remove = max_tcpci_remove,
+ .id_table = max_tcpci_id,
+};
+module_i2c_driver(max_tcpci_i2c_driver);
+
+MODULE_AUTHOR("Badhri Jagan Sridharan <badhri@google.com>");
+MODULE_DESCRIPTION("Maxim TCPCI based USB Type-C Port Controller Interface Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/typec/tcpm/tcpci_mt6360.c b/drivers/usb/typec/tcpm/tcpci_mt6360.c
new file mode 100644
index 000000000000..f1bd9e09bc87
--- /dev/null
+++ b/drivers/usb/typec/tcpm/tcpci_mt6360.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ *
+ * Author: ChiYuan Huang <cy_huang@richtek.com>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/usb/tcpm.h>
+
+#include "tcpci.h"
+
+#define MT6360_REG_VCONNCTRL1 0x8C
+#define MT6360_REG_MODECTRL2 0x8F
+#define MT6360_REG_SWRESET 0xA0
+#define MT6360_REG_DEBCTRL1 0xA1
+#define MT6360_REG_DRPCTRL1 0xA2
+#define MT6360_REG_DRPCTRL2 0xA3
+#define MT6360_REG_I2CTORST 0xBF
+#define MT6360_REG_RXCTRL2 0xCF
+#define MT6360_REG_CTDCTRL2 0xEC
+
+/* MT6360_REG_VCONNCTRL1 */
+#define MT6360_VCONNCL_ENABLE BIT(0)
+/* MT6360_REG_RXCTRL2 */
+#define MT6360_OPEN40M_ENABLE BIT(7)
+/* MT6360_REG_CTDCTRL2 */
+#define MT6360_RPONESHOT_ENABLE BIT(6)
+
+struct mt6360_tcpc_info {
+ struct tcpci_data tdata;
+ struct tcpci *tcpci;
+ struct device *dev;
+ int irq;
+};
+
+static inline int mt6360_tcpc_read16(struct regmap *regmap,
+ unsigned int reg, u16 *val)
+{
+ return regmap_raw_read(regmap, reg, val, sizeof(u16));
+}
+
+static inline int mt6360_tcpc_write16(struct regmap *regmap,
+ unsigned int reg, u16 val)
+{
+ return regmap_raw_write(regmap, reg, &val, sizeof(u16));
+}
+
+static int mt6360_tcpc_init(struct tcpci *tcpci, struct tcpci_data *tdata)
+{
+ struct regmap *regmap = tdata->regmap;
+ int ret;
+
+ ret = regmap_write(regmap, MT6360_REG_SWRESET, 0x01);
+ if (ret)
+ return ret;
+
+ /* after reset command, wait 1~2ms to wait IC action */
+ usleep_range(1000, 2000);
+
+ /* write all alert to masked */
+ ret = mt6360_tcpc_write16(regmap, TCPC_ALERT_MASK, 0);
+ if (ret)
+ return ret;
+
+ /* config I2C timeout reset enable , and timeout to 200ms */
+ ret = regmap_write(regmap, MT6360_REG_I2CTORST, 0x8F);
+ if (ret)
+ return ret;
+
+ /* config CC Detect Debounce : 26.7*val us */
+ ret = regmap_write(regmap, MT6360_REG_DEBCTRL1, 0x10);
+ if (ret)
+ return ret;
+
+ /* DRP Toggle Cycle : 51.2 + 6.4*val ms */
+ ret = regmap_write(regmap, MT6360_REG_DRPCTRL1, 4);
+ if (ret)
+ return ret;
+
+ /* DRP Duyt Ctrl : dcSRC: /1024 */
+ ret = mt6360_tcpc_write16(regmap, MT6360_REG_DRPCTRL2, 330);
+ if (ret)
+ return ret;
+
+ /* Enable VCONN Current Limit function */
+ ret = regmap_update_bits(regmap, MT6360_REG_VCONNCTRL1, MT6360_VCONNCL_ENABLE,
+ MT6360_VCONNCL_ENABLE);
+ if (ret)
+ return ret;
+
+ /* Enable cc open 40ms when pmic send vsysuv signal */
+ ret = regmap_update_bits(regmap, MT6360_REG_RXCTRL2, MT6360_OPEN40M_ENABLE,
+ MT6360_OPEN40M_ENABLE);
+ if (ret)
+ return ret;
+
+ /* Enable Rpdet oneshot detection */
+ ret = regmap_update_bits(regmap, MT6360_REG_CTDCTRL2, MT6360_RPONESHOT_ENABLE,
+ MT6360_RPONESHOT_ENABLE);
+ if (ret)
+ return ret;
+
+ /* Set shipping mode off, AUTOIDLE on */
+ return regmap_write(regmap, MT6360_REG_MODECTRL2, 0x7A);
+}
+
+static irqreturn_t mt6360_irq(int irq, void *dev_id)
+{
+ struct mt6360_tcpc_info *mti = dev_id;
+
+ return tcpci_irq(mti->tcpci);
+}
+
+static int mt6360_tcpc_probe(struct platform_device *pdev)
+{
+ struct mt6360_tcpc_info *mti;
+ int ret;
+
+ mti = devm_kzalloc(&pdev->dev, sizeof(*mti), GFP_KERNEL);
+ if (!mti)
+ return -ENOMEM;
+
+ mti->dev = &pdev->dev;
+
+ mti->tdata.regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!mti->tdata.regmap) {
+ dev_err(&pdev->dev, "Failed to get parent regmap\n");
+ return -ENODEV;
+ }
+
+ mti->irq = platform_get_irq_byname(pdev, "PD_IRQB");
+ if (mti->irq < 0)
+ return mti->irq;
+
+ mti->tdata.init = mt6360_tcpc_init;
+ mti->tcpci = tcpci_register_port(&pdev->dev, &mti->tdata);
+ if (IS_ERR(mti->tcpci)) {
+ dev_err(&pdev->dev, "Failed to register tcpci port\n");
+ return PTR_ERR(mti->tcpci);
+ }
+
+ ret = devm_request_threaded_irq(mti->dev, mti->irq, NULL, mt6360_irq, IRQF_ONESHOT,
+ dev_name(&pdev->dev), mti);
+ if (ret) {
+ dev_err(mti->dev, "Failed to register irq\n");
+ tcpci_unregister_port(mti->tcpci);
+ return ret;
+ }
+
+ device_init_wakeup(&pdev->dev, true);
+ platform_set_drvdata(pdev, mti);
+
+ return 0;
+}
+
+static int mt6360_tcpc_remove(struct platform_device *pdev)
+{
+ struct mt6360_tcpc_info *mti = platform_get_drvdata(pdev);
+
+ disable_irq(mti->irq);
+ tcpci_unregister_port(mti->tcpci);
+ return 0;
+}
+
+static int __maybe_unused mt6360_tcpc_suspend(struct device *dev)
+{
+ struct mt6360_tcpc_info *mti = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(mti->irq);
+
+ return 0;
+}
+
+static int __maybe_unused mt6360_tcpc_resume(struct device *dev)
+{
+ struct mt6360_tcpc_info *mti = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(mti->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mt6360_tcpc_pm_ops, mt6360_tcpc_suspend, mt6360_tcpc_resume);
+
+static const struct of_device_id __maybe_unused mt6360_tcpc_of_id[] = {
+ { .compatible = "mediatek,mt6360-tcpc", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mt6360_tcpc_of_id);
+
+static struct platform_driver mt6360_tcpc_driver = {
+ .driver = {
+ .name = "mt6360-tcpc",
+ .pm = &mt6360_tcpc_pm_ops,
+ .of_match_table = mt6360_tcpc_of_id,
+ },
+ .probe = mt6360_tcpc_probe,
+ .remove = mt6360_tcpc_remove,
+};
+module_platform_driver(mt6360_tcpc_driver);
+
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("MT6360 USB Type-C Port Controller Interface Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index a48e3f90d196..55535c4f66bf 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -8,8 +8,10 @@
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/device.h>
+#include <linux/hrtimer.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
+#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/power_supply.h>
@@ -28,7 +30,8 @@
#include <linux/usb/role.h>
#include <linux/usb/tcpm.h>
#include <linux/usb/typec_altmode.h>
-#include <linux/workqueue.h>
+
+#include <uapi/linux/sched/types.h>
#define FOREACH_STATE(S) \
S(INVALID_STATE), \
@@ -103,6 +106,13 @@
S(VCONN_SWAP_TURN_ON_VCONN), \
S(VCONN_SWAP_TURN_OFF_VCONN), \
\
+ S(FR_SWAP_SEND), \
+ S(FR_SWAP_SEND_TIMEOUT), \
+ S(FR_SWAP_SNK_SRC_TRANSITION_TO_OFF), \
+ S(FR_SWAP_SNK_SRC_NEW_SINK_READY), \
+ S(FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED), \
+ S(FR_SWAP_CANCEL), \
+ \
S(SNK_TRY), \
S(SNK_TRY_WAIT), \
S(SNK_TRY_WAIT_DEBOUNCE), \
@@ -124,6 +134,9 @@
S(GET_PPS_STATUS_SEND), \
S(GET_PPS_STATUS_SEND_TIMEOUT), \
\
+ S(GET_SINK_CAP), \
+ S(GET_SINK_CAP_TIMEOUT), \
+ \
S(ERROR_RECOVERY), \
S(PORT_RESET), \
S(PORT_RESET_WAIT_OFF)
@@ -167,11 +180,25 @@ enum adev_actions {
ADEV_ATTENTION,
};
+/*
+ * Initial current capability of the new source when vSafe5V is applied during PD3.0 Fast Role Swap.
+ * Based on "Table 6-14 Fixed Supply PDO - Sink" of "USB Power Delivery Specification Revision 3.0,
+ * Version 1.2"
+ */
+enum frs_typec_current {
+ FRS_NOT_SUPPORTED,
+ FRS_DEFAULT_POWER,
+ FRS_5V_1P5A,
+ FRS_5V_3A,
+};
+
/* Events from low level driver */
#define TCPM_CC_EVENT BIT(0)
#define TCPM_VBUS_EVENT BIT(1)
#define TCPM_RESET_EVENT BIT(2)
+#define TCPM_FRS_EVENT BIT(3)
+#define TCPM_SOURCING_VBUS BIT(4)
#define LOG_BUFFER_ENTRIES 1024
#define LOG_BUFFER_ENTRY_SIZE 128
@@ -181,6 +208,8 @@ enum adev_actions {
#define SVID_DISCOVERY_MAX 16
#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX)
+#define GET_SINK_CAP_RETRY_MS 100
+
struct pd_mode_data {
int svid_index; /* current SVID index */
int nsvids;
@@ -203,7 +232,7 @@ struct tcpm_port {
struct device *dev;
struct mutex lock; /* tcpm state machine lock */
- struct workqueue_struct *wq;
+ struct kthread_worker *wq;
struct typec_capability typec_caps;
struct typec_port *typec_port;
@@ -247,15 +276,19 @@ struct tcpm_port {
enum tcpm_state prev_state;
enum tcpm_state state;
enum tcpm_state delayed_state;
- unsigned long delayed_runtime;
+ ktime_t delayed_runtime;
unsigned long delay_ms;
spinlock_t pd_event_lock;
u32 pd_events;
- struct work_struct event_work;
- struct delayed_work state_machine;
- struct delayed_work vdm_state_machine;
+ struct kthread_work event_work;
+ struct hrtimer state_machine_timer;
+ struct kthread_work state_machine;
+ struct hrtimer vdm_state_machine_timer;
+ struct kthread_work vdm_state_machine;
+ struct hrtimer enable_frs_timer;
+ struct kthread_work enable_frs;
bool state_machine_running;
struct completion tx_complete;
@@ -330,6 +363,12 @@ struct tcpm_port {
/* port belongs to a self powered device */
bool self_powered;
+ /* FRS */
+ enum frs_typec_current frs_current;
+
+ /* Sink caps have been queried */
+ bool sink_cap_done;
+
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
struct mutex logbuffer_lock; /* log buffer access lock */
@@ -340,7 +379,7 @@ struct tcpm_port {
};
struct pd_rx_event {
- struct work_struct work;
+ struct kthread_work work;
struct tcpm_port *port;
struct pd_message msg;
};
@@ -914,6 +953,37 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port)
return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
}
+static void mod_tcpm_delayed_work(struct tcpm_port *port, unsigned int delay_ms)
+{
+ if (delay_ms) {
+ hrtimer_start(&port->state_machine_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL);
+ } else {
+ hrtimer_cancel(&port->state_machine_timer);
+ kthread_queue_work(port->wq, &port->state_machine);
+ }
+}
+
+static void mod_vdm_delayed_work(struct tcpm_port *port, unsigned int delay_ms)
+{
+ if (delay_ms) {
+ hrtimer_start(&port->vdm_state_machine_timer, ms_to_ktime(delay_ms),
+ HRTIMER_MODE_REL);
+ } else {
+ hrtimer_cancel(&port->vdm_state_machine_timer);
+ kthread_queue_work(port->wq, &port->vdm_state_machine);
+ }
+}
+
+static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int delay_ms)
+{
+ if (delay_ms) {
+ hrtimer_start(&port->enable_frs_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL);
+ } else {
+ hrtimer_cancel(&port->enable_frs_timer);
+ kthread_queue_work(port->wq, &port->enable_frs);
+ }
+}
+
static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state,
unsigned int delay_ms)
{
@@ -922,9 +992,8 @@ static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state,
tcpm_states[port->state], tcpm_states[state],
delay_ms);
port->delayed_state = state;
- mod_delayed_work(port->wq, &port->state_machine,
- msecs_to_jiffies(delay_ms));
- port->delayed_runtime = jiffies + msecs_to_jiffies(delay_ms);
+ mod_tcpm_delayed_work(port, delay_ms);
+ port->delayed_runtime = ktime_add(ktime_get(), ms_to_ktime(delay_ms));
port->delay_ms = delay_ms;
} else {
tcpm_log(port, "state change %s -> %s",
@@ -939,7 +1008,7 @@ static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state,
* machine.
*/
if (!port->state_machine_running)
- mod_delayed_work(port->wq, &port->state_machine, 0);
+ mod_tcpm_delayed_work(port, 0);
}
}
@@ -960,7 +1029,7 @@ static void tcpm_queue_message(struct tcpm_port *port,
enum pd_msg_request message)
{
port->queued_message = message;
- mod_delayed_work(port->wq, &port->state_machine, 0);
+ mod_tcpm_delayed_work(port, 0);
}
/*
@@ -981,7 +1050,7 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
port->vdm_retries = 0;
port->vdm_state = VDM_STATE_READY;
- mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
+ mod_vdm_delayed_work(port, 0);
}
static void tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header,
@@ -1244,8 +1313,7 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port,
port->vdm_state = VDM_STATE_WAIT_RSP_BUSY;
port->vdo_retry = (p[0] & ~VDO_CMDT_MASK) |
CMDT_INIT;
- mod_delayed_work(port->wq, &port->vdm_state_machine,
- msecs_to_jiffies(PD_T_VDM_BUSY));
+ mod_vdm_delayed_work(port, PD_T_VDM_BUSY);
return;
}
port->vdm_state = VDM_STATE_DONE;
@@ -1390,8 +1458,7 @@ static void vdm_run_state_machine(struct tcpm_port *port)
port->vdm_retries = 0;
port->vdm_state = VDM_STATE_BUSY;
timeout = vdm_ready_timeout(port->vdo_data[0]);
- mod_delayed_work(port->wq, &port->vdm_state_machine,
- timeout);
+ mod_vdm_delayed_work(port, timeout);
}
break;
case VDM_STATE_WAIT_RSP_BUSY:
@@ -1420,10 +1487,9 @@ static void vdm_run_state_machine(struct tcpm_port *port)
}
}
-static void vdm_state_machine_work(struct work_struct *work)
+static void vdm_state_machine_work(struct kthread_work *work)
{
- struct tcpm_port *port = container_of(work, struct tcpm_port,
- vdm_state_machine.work);
+ struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_state_machine);
enum vdm_states prev_state;
mutex_lock(&port->lock);
@@ -1591,6 +1657,7 @@ static int tcpm_altmode_vdm(struct typec_altmode *altmode,
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
tcpm_queue_vdm_unlocked(port, header, data, count - 1);
+
return 0;
}
@@ -1646,6 +1713,9 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
unsigned int cnt = pd_header_cnt_le(msg->header);
unsigned int rev = pd_header_rev_le(msg->header);
unsigned int i;
+ enum frs_typec_current frs_current;
+ bool frs_enable;
+ int ret;
switch (type) {
case PD_DATA_SOURCE_CAP:
@@ -1715,7 +1785,21 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
/* We don't do anything with this at the moment... */
for (i = 0; i < cnt; i++)
port->sink_caps[i] = le32_to_cpu(msg->payload[i]);
+
+ frs_current = (port->sink_caps[0] & PDO_FIXED_FRS_CURR_MASK) >>
+ PDO_FIXED_FRS_CURR_SHIFT;
+ frs_enable = frs_current && (frs_current <= port->frs_current);
+ tcpm_log(port,
+ "Port partner FRS capable partner_frs_current:%u port_frs_current:%u enable:%c",
+ frs_current, port->frs_current, frs_enable ? 'y' : 'n');
+ if (frs_enable) {
+ ret = port->tcpc->enable_frs(port->tcpc, true);
+ tcpm_log(port, "Enable FRS %s, ret:%d\n", ret ? "fail" : "success", ret);
+ }
+
port->nr_sink_caps = cnt;
+ port->sink_cap_done = true;
+ tcpm_set_state(port, SNK_READY, 0);
break;
case PD_DATA_VENDOR_DEF:
tcpm_handle_vdm_request(port, msg->payload, cnt);
@@ -1810,6 +1894,9 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
case VCONN_SWAP_WAIT_FOR_VCONN:
tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0);
break;
+ case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
+ tcpm_set_state(port, FR_SWAP_SNK_SRC_NEW_SINK_READY, 0);
+ break;
default:
break;
}
@@ -1849,6 +1936,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
-EAGAIN : -EOPNOTSUPP);
tcpm_set_state(port, VCONN_SWAP_CANCEL, 0);
break;
+ case FR_SWAP_SEND:
+ tcpm_set_state(port, FR_SWAP_CANCEL, 0);
+ break;
+ case GET_SINK_CAP:
+ port->sink_cap_done = true;
+ tcpm_set_state(port, ready_state(port), 0);
+ break;
default:
break;
}
@@ -1883,6 +1977,9 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
case VCONN_SWAP_SEND:
tcpm_set_state(port, VCONN_SWAP_START, 0);
break;
+ case FR_SWAP_SEND:
+ tcpm_set_state(port, FR_SWAP_SNK_SRC_TRANSITION_TO_OFF, 0);
+ break;
default:
break;
}
@@ -2005,7 +2102,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port,
}
}
-static void tcpm_pd_rx_handler(struct work_struct *work)
+static void tcpm_pd_rx_handler(struct kthread_work *work)
{
struct pd_rx_event *event = container_of(work,
struct pd_rx_event, work);
@@ -2067,10 +2164,10 @@ void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg)
if (!event)
return;
- INIT_WORK(&event->work, tcpm_pd_rx_handler);
+ kthread_init_work(&event->work, tcpm_pd_rx_handler);
event->port = port;
memcpy(&event->msg, msg, sizeof(*msg));
- queue_work(port->wq, &event->work);
+ kthread_queue_work(port->wq, &event->work);
}
EXPORT_SYMBOL_GPL(tcpm_pd_receive);
@@ -2123,9 +2220,9 @@ static bool tcpm_send_queued_message(struct tcpm_port *port)
} while (port->queued_message != PD_MSG_NONE);
if (port->delayed_state != INVALID_STATE) {
- if (time_is_after_jiffies(port->delayed_runtime)) {
- mod_delayed_work(port->wq, &port->state_machine,
- port->delayed_runtime - jiffies);
+ if (ktime_after(port->delayed_runtime, ktime_get())) {
+ mod_tcpm_delayed_work(port, ktime_to_ms(ktime_sub(port->delayed_runtime,
+ ktime_get())));
return true;
}
port->delayed_state = INVALID_STATE;
@@ -2783,6 +2880,10 @@ static void tcpm_reset_port(struct tcpm_port *port)
port->try_src_count = 0;
port->try_snk_count = 0;
port->usb_type = POWER_SUPPLY_USB_TYPE_C;
+ port->nr_sink_caps = 0;
+ port->sink_cap_done = false;
+ if (port->tcpc->enable_frs)
+ port->tcpc->enable_frs(port->tcpc, false);
power_supply_changed(port->psy);
}
@@ -3258,10 +3359,9 @@ static void run_state_machine(struct tcpm_port *port)
case SNK_DISCOVERY_DEBOUNCE_DONE:
if (!tcpm_port_is_disconnected(port) &&
tcpm_port_is_sink(port) &&
- time_is_after_jiffies(port->delayed_runtime)) {
+ ktime_after(port->delayed_runtime, ktime_get())) {
tcpm_set_state(port, SNK_DISCOVERY,
- jiffies_to_msecs(port->delayed_runtime -
- jiffies));
+ ktime_to_ms(ktime_sub(port->delayed_runtime, ktime_get())));
break;
}
tcpm_set_state(port, unattached_state(port), 0);
@@ -3334,10 +3434,9 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_swap_complete(port, 0);
tcpm_typec_connect(port);
tcpm_check_send_discover(port);
+ mod_enable_frs_delayed_work(port, 0);
tcpm_pps_complete(port, port->pps_status);
-
power_supply_changed(port->psy);
-
break;
/* Accessory states */
@@ -3361,9 +3460,13 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_state(port, HARD_RESET_START, 0);
break;
case HARD_RESET_START:
+ port->sink_cap_done = false;
+ if (port->tcpc->enable_frs)
+ port->tcpc->enable_frs(port->tcpc, false);
port->hard_reset_count++;
port->tcpc->set_pd_rx(port->tcpc, false);
tcpm_unregister_altmodes(port);
+ port->nr_sink_caps = 0;
port->send_discover = true;
if (port->pwr_role == TYPEC_SOURCE)
tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF,
@@ -3495,6 +3598,35 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_state(port, ready_state(port), 0);
break;
+ case FR_SWAP_SEND:
+ if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP)) {
+ tcpm_set_state(port, ERROR_RECOVERY, 0);
+ break;
+ }
+ tcpm_set_state_cond(port, FR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE);
+ break;
+ case FR_SWAP_SEND_TIMEOUT:
+ tcpm_set_state(port, ERROR_RECOVERY, 0);
+ break;
+ case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
+ tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_OFF);
+ break;
+ case FR_SWAP_SNK_SRC_NEW_SINK_READY:
+ if (port->vbus_source)
+ tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0);
+ else
+ tcpm_set_state(port, ERROR_RECOVERY, PD_T_RECEIVER_RESPONSE);
+ break;
+ case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
+ tcpm_set_pwr_role(port, TYPEC_SOURCE);
+ if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) {
+ tcpm_set_state(port, ERROR_RECOVERY, 0);
+ break;
+ }
+ tcpm_set_cc(port, tcpm_rp_cc(port));
+ tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START);
+ break;
+
/* PR_Swap states */
case PR_SWAP_ACCEPT:
tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
@@ -3573,7 +3705,7 @@ static void run_state_machine(struct tcpm_port *port)
*/
tcpm_set_pwr_role(port, TYPEC_SOURCE);
tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
- tcpm_set_state(port, SRC_STARTUP, 0);
+ tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START);
break;
case VCONN_SWAP_ACCEPT:
@@ -3618,6 +3750,12 @@ static void run_state_machine(struct tcpm_port *port)
else
tcpm_set_state(port, SNK_READY, 0);
break;
+ case FR_SWAP_CANCEL:
+ if (port->pwr_role == TYPEC_SOURCE)
+ tcpm_set_state(port, SRC_READY, 0);
+ else
+ tcpm_set_state(port, SNK_READY, 0);
+ break;
case BIST_RX:
switch (BDO_MODE_MASK(port->bist_request)) {
@@ -3652,6 +3790,14 @@ static void run_state_machine(struct tcpm_port *port)
case GET_PPS_STATUS_SEND_TIMEOUT:
tcpm_set_state(port, ready_state(port), 0);
break;
+ case GET_SINK_CAP:
+ tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP);
+ tcpm_set_state(port, GET_SINK_CAP_TIMEOUT, PD_T_SENDER_RESPONSE);
+ break;
+ case GET_SINK_CAP_TIMEOUT:
+ port->sink_cap_done = true;
+ tcpm_set_state(port, ready_state(port), 0);
+ break;
case ERROR_RECOVERY:
tcpm_swap_complete(port, -EPROTO);
tcpm_pps_complete(port, -EPROTO);
@@ -3674,10 +3820,9 @@ static void run_state_machine(struct tcpm_port *port)
}
}
-static void tcpm_state_machine_work(struct work_struct *work)
+static void tcpm_state_machine_work(struct kthread_work *work)
{
- struct tcpm_port *port = container_of(work, struct tcpm_port,
- state_machine.work);
+ struct tcpm_port *port = container_of(work, struct tcpm_port, state_machine);
enum tcpm_state prev_state;
mutex_lock(&port->lock);
@@ -3868,6 +4013,13 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1,
* Ignore it.
*/
break;
+ case FR_SWAP_SEND:
+ case FR_SWAP_SEND_TIMEOUT:
+ case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
+ case FR_SWAP_SNK_SRC_NEW_SINK_READY:
+ case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
+ /* Do nothing, CC change expected */
+ break;
case PORT_RESET:
case PORT_RESET_WAIT_OFF:
@@ -3938,6 +4090,9 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port)
case SRC_TRY_DEBOUNCE:
/* Do nothing, waiting for sink detection */
break;
+ case FR_SWAP_SNK_SRC_NEW_SINK_READY:
+ tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0);
+ break;
case PORT_RESET:
case PORT_RESET_WAIT_OFF:
@@ -4017,6 +4172,14 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port)
*/
break;
+ case FR_SWAP_SEND:
+ case FR_SWAP_SEND_TIMEOUT:
+ case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
+ case FR_SWAP_SNK_SRC_NEW_SINK_READY:
+ case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
+ /* Do nothing, vbus drop expected */
+ break;
+
default:
if (port->pwr_role == TYPEC_SINK &&
port->attached)
@@ -4041,7 +4204,7 @@ static void _tcpm_pd_hard_reset(struct tcpm_port *port)
0);
}
-static void tcpm_pd_event_handler(struct work_struct *work)
+static void tcpm_pd_event_handler(struct kthread_work *work)
{
struct tcpm_port *port = container_of(work, struct tcpm_port,
event_work);
@@ -4071,6 +4234,25 @@ static void tcpm_pd_event_handler(struct work_struct *work)
if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0)
_tcpm_cc_change(port, cc1, cc2);
}
+ if (events & TCPM_FRS_EVENT) {
+ if (port->state == SNK_READY)
+ tcpm_set_state(port, FR_SWAP_SEND, 0);
+ else
+ tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready");
+ }
+ if (events & TCPM_SOURCING_VBUS) {
+ tcpm_log(port, "sourcing vbus");
+ /*
+ * In fast role swap case TCPC autonomously sources vbus. Set vbus_source
+ * true as TCPM wouldn't have called tcpm_set_vbus.
+ *
+ * When vbus is sourced on the command on TCPM i.e. TCPM called
+ * tcpm_set_vbus to source vbus, vbus_source would already be true.
+ */
+ port->vbus_source = true;
+ _tcpm_pd_vbus_on(port);
+ }
+
spin_lock(&port->pd_event_lock);
}
spin_unlock(&port->pd_event_lock);
@@ -4082,7 +4264,7 @@ void tcpm_cc_change(struct tcpm_port *port)
spin_lock(&port->pd_event_lock);
port->pd_events |= TCPM_CC_EVENT;
spin_unlock(&port->pd_event_lock);
- queue_work(port->wq, &port->event_work);
+ kthread_queue_work(port->wq, &port->event_work);
}
EXPORT_SYMBOL_GPL(tcpm_cc_change);
@@ -4091,7 +4273,7 @@ void tcpm_vbus_change(struct tcpm_port *port)
spin_lock(&port->pd_event_lock);
port->pd_events |= TCPM_VBUS_EVENT;
spin_unlock(&port->pd_event_lock);
- queue_work(port->wq, &port->event_work);
+ kthread_queue_work(port->wq, &port->event_work);
}
EXPORT_SYMBOL_GPL(tcpm_vbus_change);
@@ -4100,10 +4282,54 @@ void tcpm_pd_hard_reset(struct tcpm_port *port)
spin_lock(&port->pd_event_lock);
port->pd_events = TCPM_RESET_EVENT;
spin_unlock(&port->pd_event_lock);
- queue_work(port->wq, &port->event_work);
+ kthread_queue_work(port->wq, &port->event_work);
}
EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset);
+void tcpm_sink_frs(struct tcpm_port *port)
+{
+ spin_lock(&port->pd_event_lock);
+ port->pd_events = TCPM_FRS_EVENT;
+ spin_unlock(&port->pd_event_lock);
+ kthread_queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_sink_frs);
+
+void tcpm_sourcing_vbus(struct tcpm_port *port)
+{
+ spin_lock(&port->pd_event_lock);
+ port->pd_events = TCPM_SOURCING_VBUS;
+ spin_unlock(&port->pd_event_lock);
+ kthread_queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus);
+
+static void tcpm_enable_frs_work(struct kthread_work *work)
+{
+ struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs);
+
+ mutex_lock(&port->lock);
+ /* Not FRS capable */
+ if (!port->connected || port->port_type != TYPEC_PORT_DRP ||
+ port->pwr_opmode != TYPEC_PWR_MODE_PD ||
+ !port->tcpc->enable_frs ||
+ /* Sink caps queried */
+ port->sink_cap_done || port->negotiated_rev < PD_REV30)
+ goto unlock;
+
+ /* Send when the state machine is idle */
+ if (port->state != SNK_READY || port->vdm_state != VDM_STATE_DONE || port->send_discover)
+ goto resched;
+
+ tcpm_set_state(port, GET_SINK_CAP, 0);
+ port->sink_cap_done = true;
+
+resched:
+ mod_enable_frs_delayed_work(port, GET_SINK_CAP_RETRY_MS);
+unlock:
+ mutex_unlock(&port->lock);
+}
+
static int tcpm_dr_set(struct typec_port *p, enum typec_data_role data)
{
struct tcpm_port *port = typec_get_drvdata(p);
@@ -4511,7 +4737,7 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
{
const char *cap_str;
int ret;
- u32 mw;
+ u32 mw, frs_current;
if (!fwnode)
return -EINVAL;
@@ -4580,6 +4806,13 @@ sink:
port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
+ /* FRS can only be supported byb DRP ports */
+ if (port->port_type == TYPEC_PORT_DRP) {
+ ret = fwnode_property_read_u32(fwnode, "frs-typec-current", &frs_current);
+ if (ret >= 0 && frs_current <= FRS_5V_3A)
+ port->frs_current = frs_current;
+ }
+
return 0;
}
@@ -4808,6 +5041,30 @@ static int devm_tcpm_psy_register(struct tcpm_port *port)
return PTR_ERR_OR_ZERO(port->psy);
}
+static enum hrtimer_restart state_machine_timer_handler(struct hrtimer *timer)
+{
+ struct tcpm_port *port = container_of(timer, struct tcpm_port, state_machine_timer);
+
+ kthread_queue_work(port->wq, &port->state_machine);
+ return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart vdm_state_machine_timer_handler(struct hrtimer *timer)
+{
+ struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_state_machine_timer);
+
+ kthread_queue_work(port->wq, &port->vdm_state_machine);
+ return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer)
+{
+ struct tcpm_port *port = container_of(timer, struct tcpm_port, enable_frs_timer);
+
+ kthread_queue_work(port->wq, &port->enable_frs);
+ return HRTIMER_NORESTART;
+}
+
struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
{
struct tcpm_port *port;
@@ -4829,12 +5086,21 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
mutex_init(&port->lock);
mutex_init(&port->swap_lock);
- port->wq = create_singlethread_workqueue(dev_name(dev));
- if (!port->wq)
- return ERR_PTR(-ENOMEM);
- INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work);
- INIT_DELAYED_WORK(&port->vdm_state_machine, vdm_state_machine_work);
- INIT_WORK(&port->event_work, tcpm_pd_event_handler);
+ port->wq = kthread_create_worker(0, dev_name(dev));
+ if (IS_ERR(port->wq))
+ return ERR_CAST(port->wq);
+ sched_set_fifo(port->wq->task);
+
+ kthread_init_work(&port->state_machine, tcpm_state_machine_work);
+ kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work);
+ kthread_init_work(&port->event_work, tcpm_pd_event_handler);
+ kthread_init_work(&port->enable_frs, tcpm_enable_frs_work);
+ hrtimer_init(&port->state_machine_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ port->state_machine_timer.function = state_machine_timer_handler;
+ hrtimer_init(&port->vdm_state_machine_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ port->vdm_state_machine_timer.function = vdm_state_machine_timer_handler;
+ hrtimer_init(&port->enable_frs_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ port->enable_frs_timer.function = enable_frs_timer_handler;
spin_lock_init(&port->pd_event_lock);
@@ -4886,7 +5152,7 @@ out_role_sw_put:
usb_role_switch_put(port->role_sw);
out_destroy_wq:
tcpm_debugfs_exit(port);
- destroy_workqueue(port->wq);
+ kthread_destroy_worker(port->wq);
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(tcpm_register_port);
@@ -4901,7 +5167,7 @@ void tcpm_unregister_port(struct tcpm_port *port)
typec_unregister_port(port->typec_port);
usb_role_switch_put(port->role_sw);
tcpm_debugfs_exit(port);
- destroy_workqueue(port->wq);
+ kthread_destroy_worker(port->wq);
}
EXPORT_SYMBOL_GPL(tcpm_unregister_port);