diff options
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r-- | drivers/usb/typec/Kconfig | 24 | ||||
-rw-r--r-- | drivers/usb/typec/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/typec/altmodes/displayport.c | 2 | ||||
-rw-r--r-- | drivers/usb/typec/class.c | 15 | ||||
-rw-r--r-- | drivers/usb/typec/hd3ss3220.c | 18 | ||||
-rw-r--r-- | drivers/usb/typec/mux/Kconfig | 1 | ||||
-rw-r--r-- | drivers/usb/typec/mux/intel_pmc_mux.c | 207 | ||||
-rw-r--r-- | drivers/usb/typec/qcom-pmic-typec.c | 262 | ||||
-rw-r--r-- | drivers/usb/typec/stusb160x.c | 875 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/Kconfig | 14 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/Makefile | 14 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci.c | 113 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci.h | 25 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci_maxim.c | 503 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci_mt6360.c | 212 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpm.c | 362 |
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", ®); + 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, ®); 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, ®_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); |