diff options
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r-- | drivers/usb/typec/Kconfig | 12 | ||||
-rw-r--r-- | drivers/usb/typec/Makefile | 1 | ||||
-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/stusb160x.c | 875 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/Kconfig | 8 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci.c | 83 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci.h | 13 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci_mt6360.c | 212 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpm.c | 133 |
14 files changed, 1484 insertions, 97 deletions
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 559dd06117e7..eee8536ae600 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -73,6 +73,18 @@ 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. + 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..671bc2d3cd6a 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -6,4 +6,5 @@ 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_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/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..58a64e1bf627 100644 --- a/drivers/usb/typec/tcpm/Kconfig +++ b/drivers/usb/typec/tcpm/Kconfig @@ -27,6 +27,14 @@ 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. + endif # TYPEC_TCPCI config TYPEC_FUSB302 diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile index a5ff6c8eb892..7592ccb8c526 100644 --- a/drivers/usb/typec/tcpm/Makefile +++ b/drivers/usb/typec/tcpm/Makefile @@ -5,3 +5,4 @@ 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 diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index bd80e03b2b6f..7d9491ba62fb 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -191,12 +191,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 @@ -330,23 +365,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)); + + 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; - if (cnt > 0) { - ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, - &msg->payload, cnt); + 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; diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h index 11c36d086c86..cf9d8b63adcb 100644 --- a/drivers/usb/typec/tcpm/tcpci.h +++ b/drivers/usb/typec/tcpm/tcpci.h @@ -16,6 +16,7 @@ #define TCPC_PD_INT_REV 0xa #define TCPC_ALERT 0x10 +#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 +33,10 @@ #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_CONFIG_STD_OUTPUT 0x18 #define TCPC_TCPC_CTRL 0x19 @@ -123,9 +128,17 @@ #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); 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..92806547f485 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), \ @@ -203,7 +206,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 +250,17 @@ 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; bool state_machine_running; struct completion tx_complete; @@ -340,7 +345,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 +919,27 @@ 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 tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, unsigned int delay_ms) { @@ -922,9 +948,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 +964,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 +985,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 +1006,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 +1269,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 +1414,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 +1443,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 +1613,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; } @@ -2005,7 +2028,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 +2090,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 +2146,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; @@ -3258,10 +3281,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); @@ -3573,7 +3595,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: @@ -3674,10 +3696,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); @@ -4041,7 +4062,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); @@ -4082,7 +4103,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 +4112,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,7 +4121,7 @@ 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); @@ -4808,6 +4829,22 @@ 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; +} + struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) { struct tcpm_port *port; @@ -4829,12 +4866,18 @@ 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); + 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; spin_lock_init(&port->pd_event_lock); @@ -4886,7 +4929,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 +4944,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); |