diff options
Diffstat (limited to 'drivers/usb')
38 files changed, 2705 insertions, 989 deletions
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index 351ede4b5de2..f337aaea7604 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -116,3 +116,20 @@ config USB_AUTOSUSPEND_DELAY The default value Linux has always had is 2 seconds. Change this value if you want a different delay and cannot modify the command line or module parameter. + +config USB_DEFAULT_AUTHORIZATION_MODE + int "Default authorization mode for USB devices" + range 0 2 + default 1 + depends on USB + help + Select the default USB device authorization mode. Can be overridden + with usbcore.authorized_default command line or module parameter. + + The available values have the following meanings: + 0 is unauthorized for all devices + 1 is authorized for all devices (default) + 2 is authorized for internal devices + + If the default value is too permissive but you are unsure which mode + to use, say 2. diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index e01b1913d02b..e02ba15f6e34 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1710,9 +1710,7 @@ int usb_autoresume_device(struct usb_device *udev) { int status; - status = pm_runtime_get_sync(&udev->dev); - if (status < 0) - pm_runtime_put_sync(&udev->dev); + status = pm_runtime_resume_and_get(&udev->dev); dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&udev->dev.power.usage_count), status); @@ -1818,9 +1816,7 @@ int usb_autopm_get_interface(struct usb_interface *intf) { int status; - status = pm_runtime_get_sync(&intf->dev); - if (status < 0) - pm_runtime_put_sync(&intf->dev); + status = pm_runtime_resume_and_get(&intf->dev); dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&intf->dev.power.usage_count), status); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 12b6dfeaf658..14fc28c4cf48 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -357,12 +357,10 @@ static const u8 ss_rh_config_descriptor[] = { #define USB_AUTHORIZE_ALL 1 #define USB_AUTHORIZE_INTERNAL 2 -static int authorized_default = USB_AUTHORIZE_WIRED; +static int authorized_default = CONFIG_USB_DEFAULT_AUTHORIZATION_MODE; module_param(authorized_default, int, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(authorized_default, - "Default USB device authorization: 0 is not authorized, 1 is " - "authorized, 2 is authorized for internal devices, -1 is " - "authorized (default, same as 1)"); + "Default USB device authorization: 0 is not authorized, 1 is authorized (default), 2 is authorized for internal devices, -1 is authorized (same as 1)"); /*-------------------------------------------------------------------------*/ /** @@ -2794,10 +2792,16 @@ int usb_add_hcd(struct usb_hcd *hcd, struct usb_device *rhdev; struct usb_hcd *shared_hcd; - if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) { - hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev); - if (IS_ERR(hcd->phy_roothub)) - return PTR_ERR(hcd->phy_roothub); + if (!hcd->skip_phy_initialization) { + if (usb_hcd_is_primary_hcd(hcd)) { + hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); + } else { + hcd->phy_roothub = usb_phy_roothub_alloc_usb3_phy(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); + } retval = usb_phy_roothub_init(hcd->phy_roothub); if (retval) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index e38a4124f610..3ee8455585b6 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -37,6 +37,7 @@ #include <asm/byteorder.h> #include "hub.h" +#include "phy.h" #include "otg_productlist.h" #define USB_VENDOR_GENESYS_LOGIC 0x05e3 @@ -634,6 +635,34 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, ret = 0; } mutex_unlock(&hub->status_mutex); + + /* + * There is no need to lock status_mutex here, because status_mutex + * protects hub->status, and the phy driver only checks the port + * status without changing the status. + */ + if (!ret) { + struct usb_device *hdev = hub->hdev; + + /* + * Only roothub will be notified of connection changes, + * since the USB PHY only cares about changes at the next + * level. + */ + if (is_root_hub(hdev)) { + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + bool connect; + bool connect_change; + + connect_change = *change & USB_PORT_STAT_C_CONNECTION; + connect = *status & USB_PORT_STAT_CONNECTION; + if (connect_change && connect) + usb_phy_roothub_notify_connect(hcd->phy_roothub, port1 - 1); + else if (connect_change) + usb_phy_roothub_notify_disconnect(hcd->phy_roothub, port1 - 1); + } + } + return ret; } diff --git a/drivers/usb/core/phy.c b/drivers/usb/core/phy.c index fb1588e7c282..faa20054ad5a 100644 --- a/drivers/usb/core/phy.c +++ b/drivers/usb/core/phy.c @@ -19,6 +19,30 @@ struct usb_phy_roothub { struct list_head list; }; +/* Allocate the roothub_entry by specific name of phy */ +static int usb_phy_roothub_add_phy_by_name(struct device *dev, const char *name, + struct list_head *list) +{ + struct usb_phy_roothub *roothub_entry; + struct phy *phy; + + phy = devm_of_phy_get(dev, dev->of_node, name); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL); + if (!roothub_entry) + return -ENOMEM; + + INIT_LIST_HEAD(&roothub_entry->list); + + roothub_entry->phy = phy; + + list_add_tail(&roothub_entry->list, list); + + return 0; +} + static int usb_phy_roothub_add_phy(struct device *dev, int index, struct list_head *list) { @@ -65,6 +89,9 @@ struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) INIT_LIST_HEAD(&phy_roothub->list); + if (!usb_phy_roothub_add_phy_by_name(dev, "usb2-phy", &phy_roothub->list)) + return phy_roothub; + for (i = 0; i < num_phys; i++) { err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list); if (err) @@ -75,6 +102,41 @@ struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) } EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc); +/** + * usb_phy_roothub_alloc_usb3_phy - alloc the roothub + * @dev: the device of the host controller + * + * Allocate the usb phy roothub if the host use a generic usb3-phy. + * + * Return: On success, a pointer to the usb_phy_roothub. Otherwise, + * %NULL if no use usb3 phy or %-ENOMEM if out of memory. + */ +struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev) +{ + struct usb_phy_roothub *phy_roothub; + int num_phys; + + if (!IS_ENABLED(CONFIG_GENERIC_PHY)) + return NULL; + + num_phys = of_count_phandle_with_args(dev->of_node, "phys", + "#phy-cells"); + if (num_phys <= 0) + return NULL; + + phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL); + if (!phy_roothub) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&phy_roothub->list); + + if (!usb_phy_roothub_add_phy_by_name(dev, "usb3-phy", &phy_roothub->list)) + return phy_roothub; + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc_usb3_phy); + int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub) { struct usb_phy_roothub *roothub_entry; @@ -172,6 +234,64 @@ int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub) } EXPORT_SYMBOL_GPL(usb_phy_roothub_calibrate); +/** + * usb_phy_roothub_notify_connect() - connect notification + * @phy_roothub: the phy of roothub, if the host use a generic phy. + * @port: the port index for connect + * + * If the phy needs to get connection status, the callback can be used. + * Returns: %0 if successful, a negative error code otherwise + */ +int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_notify_connect(roothub_entry->phy, port); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_connect); + +/** + * usb_phy_roothub_notify_disconnect() - disconnect notification + * @phy_roothub: the phy of roothub, if the host use a generic phy. + * @port: the port index for disconnect + * + * If the phy needs to get connection status, the callback can be used. + * Returns: %0 if successful, a negative error code otherwise + */ +int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_notify_disconnect(roothub_entry->phy, port); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_disconnect); + int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub) { struct usb_phy_roothub *roothub_entry; diff --git a/drivers/usb/core/phy.h b/drivers/usb/core/phy.h index 20a267cd986b..88b49c0ea6b5 100644 --- a/drivers/usb/core/phy.h +++ b/drivers/usb/core/phy.h @@ -12,6 +12,7 @@ struct device; struct usb_phy_roothub; struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev); +struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev); int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub); int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub); @@ -19,6 +20,8 @@ int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub); int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub, enum phy_mode mode); int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub); +int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port); +int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port); int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub); void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub); diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 3e55838c0001..3b68e8e45b8b 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1626,6 +1626,9 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->dis_split_quirk = device_property_read_bool(dev, "snps,dis-split-quirk"); + dwc->host_vbus_glitches_quirk = device_property_read_bool(dev, + "snps,host-vbus-glitches-quirk"); + dwc->lpm_nyet_threshold = lpm_nyet_threshold; dwc->tx_de_emphasis = tx_de_emphasis; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index e3eea965e57b..df544ec730d2 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1132,6 +1132,7 @@ struct dwc3_scratchpad_array { * 2 - No de-emphasis * 3 - Reserved * @dis_metastability_quirk: set to disable metastability quirk. + * @host_vbus_glitches_quirk: set to avoid vbus glitch during xhci reset. * @dis_split_quirk: set to disable split boundary. * @wakeup_configured: set if the device is configured for remote wakeup. * @suspended: set to track suspend event due to U3/L2. @@ -1353,6 +1354,7 @@ struct dwc3 { unsigned tx_de_emphasis:2; unsigned dis_metastability_quirk:1; + unsigned host_vbus_glitches_quirk:1; unsigned dis_split_quirk:1; unsigned async_callbacks:1; diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index d1539fc9eabd..9cf9ee1b637b 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -52,8 +52,7 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) if (of_device_is_compatible(np, "rockchip,rk3399-dwc3")) simple->need_reset = true; - simple->resets = of_reset_control_array_get(np, false, true, - true); + simple->resets = of_reset_control_array_get_optional_exclusive(np); if (IS_ERR(simple->resets)) { ret = PTR_ERR(simple->resets); dev_err(dev, "failed to get device resets, err=%d\n", ret); diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 43230915323c..ae189b7a4f8b 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -11,8 +11,52 @@ #include <linux/of.h> #include <linux/platform_device.h> +#include "../host/xhci-port.h" +#include "../host/xhci-ext-caps.h" +#include "../host/xhci-caps.h" #include "core.h" +#define XHCI_HCSPARAMS1 0x4 +#define XHCI_PORTSC_BASE 0x400 + +/** + * dwc3_power_off_all_roothub_ports - Power off all Root hub ports + * @dwc: Pointer to our controller context structure + */ +static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) +{ + void __iomem *xhci_regs; + u32 op_regs_base; + int port_num; + u32 offset; + u32 reg; + int i; + + /* xhci regs is not mapped yet, do it temperary here */ + if (dwc->xhci_resources[0].start) { + xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); + if (IS_ERR(xhci_regs)) { + dev_err(dwc->dev, "Failed to ioremap xhci_regs\n"); + return; + } + + op_regs_base = HC_LENGTH(readl(xhci_regs)); + reg = readl(xhci_regs + XHCI_HCSPARAMS1); + port_num = HCS_MAX_PORTS(reg); + + for (i = 1; i <= port_num; i++) { + offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); + reg = readl(xhci_regs + offset); + reg &= ~PORT_POWER; + writel(reg, xhci_regs + offset); + } + + iounmap(xhci_regs); + } else { + dev_err(dwc->dev, "xhci base reg invalid\n"); + } +} + static void dwc3_host_fill_xhci_irq_res(struct dwc3 *dwc, int irq, char *name) { @@ -66,6 +110,13 @@ int dwc3_host_init(struct dwc3 *dwc) int ret, irq; int prop_idx = 0; + /* + * Some platforms need to power off all Root hub ports immediately after DWC3 set to host + * mode to avoid VBUS glitch happen when xhci get reset later. + */ + if (dwc->host_vbus_glitches_quirk) + dwc3_power_off_all_roothub_ports(dwc); + irq = dwc3_host_get_irq(dwc); if (irq < 0) return irq; diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 6bff6cb93789..be3851cffb73 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -3445,6 +3445,25 @@ static inline struct f_fs_opts *to_ffs_opts(struct config_item *item) func_inst.group); } +static ssize_t f_fs_opts_ready_show(struct config_item *item, char *page) +{ + struct f_fs_opts *opts = to_ffs_opts(item); + int ready; + + ffs_dev_lock(); + ready = opts->dev->desc_ready; + ffs_dev_unlock(); + + return sprintf(page, "%d\n", ready); +} + +CONFIGFS_ATTR_RO(f_fs_opts_, ready); + +static struct configfs_attribute *ffs_attrs[] = { + &f_fs_opts_attr_ready, + NULL, +}; + static void ffs_attr_release(struct config_item *item) { struct f_fs_opts *opts = to_ffs_opts(item); @@ -3458,6 +3477,7 @@ static struct configfs_item_operations ffs_item_ops = { static const struct config_item_type ffs_func_type = { .ct_item_ops = &ffs_item_ops, + .ct_attrs = ffs_attrs, .ct_owner = THIS_MODULE, }; diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index 6c47ab0a491d..ad145a54ca74 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -66,6 +66,15 @@ struct orion_ehci_hcd { static struct hc_driver __read_mostly ehci_orion_hc_driver; /* + * Legacy DMA mask is 32 bit. + * AC5 has the DDR starting at 8GB, hence it requires + * a larger (34-bit) DMA mask, in order for DMA allocations + * to succeed: + */ +static const u64 dma_mask_orion = DMA_BIT_MASK(32); +static const u64 dma_mask_ac5 = DMA_BIT_MASK(34); + +/* * Implement Orion USB controller specification guidelines */ static void orion_usb_phy_v1_setup(struct usb_hcd *hcd) @@ -211,6 +220,7 @@ static int ehci_orion_drv_probe(struct platform_device *pdev) int irq, err; enum orion_ehci_phy_ver phy_version; struct orion_ehci_hcd *priv; + u64 *dma_mask_ptr; if (usb_disabled()) return -ENODEV; @@ -228,7 +238,8 @@ static int ehci_orion_drv_probe(struct platform_device *pdev) * set. Since shared usb code relies on it, set it here for * now. Once we have dma capability bindings this can go away. */ - err = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + dma_mask_ptr = (u64 *)of_device_get_match_data(&pdev->dev); + err = dma_coerce_mask_and_coherent(&pdev->dev, *dma_mask_ptr); if (err) goto err; @@ -332,8 +343,9 @@ static void ehci_orion_drv_remove(struct platform_device *pdev) } static const struct of_device_id ehci_orion_dt_ids[] = { - { .compatible = "marvell,orion-ehci", }, - { .compatible = "marvell,armada-3700-ehci", }, + { .compatible = "marvell,orion-ehci", .data = &dma_mask_orion}, + { .compatible = "marvell,armada-3700-ehci", .data = &dma_mask_orion}, + { .compatible = "marvell,ac5-ehci", .data = &dma_mask_ac5}, {}, }; MODULE_DEVICE_TABLE(of, ehci_orion_dt_ids); diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h new file mode 100644 index 000000000000..9e94cebf4a56 --- /dev/null +++ b/drivers/usb/host/xhci-caps.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* hc_capbase bitmasks */ +/* bits 7:0 - how long is the Capabilities register */ +#define HC_LENGTH(p) XHCI_HC_LENGTH(p) +/* bits 31:16 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) + +/* HCSPARAMS1 - hcs_params1 - bitmasks */ +/* bits 0:7, Max Device Slots */ +#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +#define HCS_SLOTS_MASK 0xff +/* bits 8:18, Max Interrupters */ +#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +/* bits 24:31, Max Ports - max value is 0x7F = 127 ports */ +#define HCS_MAX_PORTS(p) (((p) >> 24) & 0x7f) + +/* HCSPARAMS2 - hcs_params2 - bitmasks */ +/* bits 0:3, frames or uframes that SW needs to queue transactions + * ahead of the HW to meet periodic deadlines */ +#define HCS_IST(p) (((p) >> 0) & 0xf) +/* bits 4:7, max number of Event Ring segments */ +#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) +/* bits 21:25 Hi 5 bits of Scratchpad buffers SW must allocate for the HW */ +/* bit 26 Scratchpad restore - for save/restore HW state - not used yet */ +/* bits 27:31 Lo 5 bits of Scratchpad buffers SW must allocate for the HW */ +#define HCS_MAX_SCRATCHPAD(p) ((((p) >> 16) & 0x3e0) | (((p) >> 27) & 0x1f)) + +/* HCSPARAMS3 - hcs_params3 - bitmasks */ +/* bits 0:7, Max U1 to U0 latency for the roothub ports */ +#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) +/* bits 16:31, Max U2 to U0 latency for the roothub ports */ +#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) + +/* HCCPARAMS - hcc_params - bitmasks */ +/* true: HC can use 64-bit address pointers */ +#define HCC_64BIT_ADDR(p) ((p) & (1 << 0)) +/* true: HC can do bandwidth negotiation */ +#define HCC_BANDWIDTH_NEG(p) ((p) & (1 << 1)) +/* true: HC uses 64-byte Device Context structures + * FIXME 64-byte context structures aren't supported yet. + */ +#define HCC_64BYTE_CONTEXT(p) ((p) & (1 << 2)) +/* true: HC has port power switches */ +#define HCC_PPC(p) ((p) & (1 << 3)) +/* true: HC has port indicators */ +#define HCS_INDICATOR(p) ((p) & (1 << 4)) +/* true: HC has Light HC Reset Capability */ +#define HCC_LIGHT_RESET(p) ((p) & (1 << 5)) +/* true: HC supports latency tolerance messaging */ +#define HCC_LTC(p) ((p) & (1 << 6)) +/* true: no secondary Stream ID Support */ +#define HCC_NSS(p) ((p) & (1 << 7)) +/* true: HC supports Stopped - Short Packet */ +#define HCC_SPC(p) ((p) & (1 << 9)) +/* true: HC has Contiguous Frame ID Capability */ +#define HCC_CFC(p) ((p) & (1 << 11)) +/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15 */ +#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) +/* Extended Capabilities pointer from PCI base - section 5.3.6 */ +#define HCC_EXT_CAPS(p) XHCI_HCC_EXT_CAPS(p) + +#define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32) + +/* db_off bitmask - bits 0:1 reserved */ +#define DBOFF_MASK (~0x3) + +/* run_regs_off bitmask - bits 0:4 reserved */ +#define RTSOFF_MASK (~0x1f) + +/* HCCPARAMS2 - hcc_params2 - bitmasks */ +/* true: HC supports U3 entry Capability */ +#define HCC2_U3C(p) ((p) & (1 << 0)) +/* true: HC supports Configure endpoint command Max exit latency too large */ +#define HCC2_CMC(p) ((p) & (1 << 1)) +/* true: HC supports Force Save context Capability */ +#define HCC2_FSC(p) ((p) & (1 << 2)) +/* true: HC supports Compliance Transition Capability */ +#define HCC2_CTC(p) ((p) & (1 << 3)) +/* true: HC support Large ESIT payload Capability > 48k */ +#define HCC2_LEC(p) ((p) & (1 << 4)) +/* true: HC support Configuration Information Capability */ +#define HCC2_CIC(p) ((p) & (1 << 5)) +/* true: HC support Extended TBC Capability, Isoc burst count > 65535 */ +#define HCC2_ETC(p) ((p) & (1 << 6)) diff --git a/drivers/usb/host/xhci-port.h b/drivers/usb/host/xhci-port.h new file mode 100644 index 000000000000..f19efb966d18 --- /dev/null +++ b/drivers/usb/host/xhci-port.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* PORTSC - Port Status and Control Register - port_status_base bitmasks */ +/* true: device connected */ +#define PORT_CONNECT (1 << 0) +/* true: port enabled */ +#define PORT_PE (1 << 1) +/* bit 2 reserved and zeroed */ +/* true: port has an over-current condition */ +#define PORT_OC (1 << 3) +/* true: port reset signaling asserted */ +#define PORT_RESET (1 << 4) +/* Port Link State - bits 5:8 + * A read gives the current link PM state of the port, + * a write with Link State Write Strobe set sets the link state. + */ +#define PORT_PLS_MASK (0xf << 5) +#define XDEV_U0 (0x0 << 5) +#define XDEV_U1 (0x1 << 5) +#define XDEV_U2 (0x2 << 5) +#define XDEV_U3 (0x3 << 5) +#define XDEV_DISABLED (0x4 << 5) +#define XDEV_RXDETECT (0x5 << 5) +#define XDEV_INACTIVE (0x6 << 5) +#define XDEV_POLLING (0x7 << 5) +#define XDEV_RECOVERY (0x8 << 5) +#define XDEV_HOT_RESET (0x9 << 5) +#define XDEV_COMP_MODE (0xa << 5) +#define XDEV_TEST_MODE (0xb << 5) +#define XDEV_RESUME (0xf << 5) + +/* true: port has power (see HCC_PPC) */ +#define PORT_POWER (1 << 9) +/* bits 10:13 indicate device speed: + * 0 - undefined speed - port hasn't be initialized by a reset yet + * 1 - full speed + * 2 - low speed + * 3 - high speed + * 4 - super speed + * 5-15 reserved + */ +#define DEV_SPEED_MASK (0xf << 10) +#define XDEV_FS (0x1 << 10) +#define XDEV_LS (0x2 << 10) +#define XDEV_HS (0x3 << 10) +#define XDEV_SS (0x4 << 10) +#define XDEV_SSP (0x5 << 10) +#define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0<<10)) +#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) +#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_LS) +#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) +#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) +#define DEV_SUPERSPEEDPLUS(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP) +#define DEV_SUPERSPEED_ANY(p) (((p) & DEV_SPEED_MASK) >= XDEV_SS) +#define DEV_PORT_SPEED(p) (((p) >> 10) & 0x0f) + +/* Bits 20:23 in the Slot Context are the speed for the device */ +#define SLOT_SPEED_FS (XDEV_FS << 10) +#define SLOT_SPEED_LS (XDEV_LS << 10) +#define SLOT_SPEED_HS (XDEV_HS << 10) +#define SLOT_SPEED_SS (XDEV_SS << 10) +#define SLOT_SPEED_SSP (XDEV_SSP << 10) +/* Port Indicator Control */ +#define PORT_LED_OFF (0 << 14) +#define PORT_LED_AMBER (1 << 14) +#define PORT_LED_GREEN (2 << 14) +#define PORT_LED_MASK (3 << 14) +/* Port Link State Write Strobe - set this when changing link state */ +#define PORT_LINK_STROBE (1 << 16) +/* true: connect status change */ +#define PORT_CSC (1 << 17) +/* true: port enable change */ +#define PORT_PEC (1 << 18) +/* true: warm reset for a USB 3.0 device is done. A "hot" reset puts the port + * into an enabled state, and the device into the default state. A "warm" reset + * also resets the link, forcing the device through the link training sequence. + * SW can also look at the Port Reset register to see when warm reset is done. + */ +#define PORT_WRC (1 << 19) +/* true: over-current change */ +#define PORT_OCC (1 << 20) +/* true: reset change - 1 to 0 transition of PORT_RESET */ +#define PORT_RC (1 << 21) +/* port link status change - set on some port link state transitions: + * Transition Reason + * ------------------------------------------------------------------------------ + * - U3 to Resume Wakeup signaling from a device + * - Resume to Recovery to U0 USB 3.0 device resume + * - Resume to U0 USB 2.0 device resume + * - U3 to Recovery to U0 Software resume of USB 3.0 device complete + * - U3 to U0 Software resume of USB 2.0 device complete + * - U2 to U0 L1 resume of USB 2.1 device complete + * - U0 to U0 (???) L1 entry rejection by USB 2.1 device + * - U0 to disabled L1 entry error with USB 2.1 device + * - Any state to inactive Error on USB 3.0 port + */ +#define PORT_PLC (1 << 22) +/* port configure error change - port failed to configure its link partner */ +#define PORT_CEC (1 << 23) +#define PORT_CHANGE_MASK (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ + PORT_RC | PORT_PLC | PORT_CEC) + + +/* Cold Attach Status - xHC can set this bit to report device attached during + * Sx state. Warm port reset should be perfomed to clear this bit and move port + * to connected state. + */ +#define PORT_CAS (1 << 24) +/* wake on connect (enable) */ +#define PORT_WKCONN_E (1 << 25) +/* wake on disconnect (enable) */ +#define PORT_WKDISC_E (1 << 26) +/* wake on over-current (enable) */ +#define PORT_WKOC_E (1 << 27) +/* bits 28:29 reserved */ +/* true: device is non-removable - for USB 3.0 roothub emulation */ +#define PORT_DEV_REMOVE (1 << 30) +/* Initiate a warm port reset - complete when PORT_WRC is '1' */ +#define PORT_WR (1 << 31) + +/* We mark duplicate entries with -1 */ +#define DUPLICATE_ENTRY ((u8)(-1)) + +/* Port Power Management Status and Control - port_power_base bitmasks */ +/* Inactivity timer value for transitions into U1, in microseconds. + * Timeout can be up to 127us. 0xFF means an infinite timeout. + */ +#define PORT_U1_TIMEOUT(p) ((p) & 0xff) +#define PORT_U1_TIMEOUT_MASK 0xff +/* Inactivity timer value for transitions into U2 */ +#define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) +#define PORT_U2_TIMEOUT_MASK (0xff << 8) +/* Bits 24:31 for port testing */ + +/* USB2 Protocol PORTSPMSC */ +#define PORT_L1S_MASK 7 +#define PORT_L1S_SUCCESS 1 +#define PORT_RWE (1 << 3) +#define PORT_HIRD(p) (((p) & 0xf) << 4) +#define PORT_HIRD_MASK (0xf << 4) +#define PORT_L1DS_MASK (0xff << 8) +#define PORT_L1DS(p) (((p) & 0xff) << 8) +#define PORT_HLE (1 << 16) +#define PORT_TEST_MODE_SHIFT 28 + +/* USB3 Protocol PORTLI Port Link Information */ +#define PORT_RX_LANES(p) (((p) >> 16) & 0xf) +#define PORT_TX_LANES(p) (((p) >> 20) & 0xf) + +/* USB2 Protocol PORTHLPMC */ +#define PORT_HIRDM(p)((p) & 3) +#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2) +#define PORT_BESLD(p)(((p) & 0xf) << 10) + +/* use 512 microseconds as USB2 LPM L1 default timeout. */ +#define XHCI_L1_TIMEOUT 512 + +/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency. + * Safe to use with mixed HIRD and BESL systems (host and device) and is used + * by other operating systems. + * + * XHCI 1.0 errata 8/14/12 Table 13 notes: + * "Software should choose xHC BESL/BESLD field values that do not violate a + * device's resume latency requirements, + * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached, + * or not program values < '4' if BLC = '0' and a BESL device is attached. + */ +#define XHCI_DEFAULT_BESL 4 + +/* + * USB3 specification define a 360ms tPollingLFPSTiemout for USB3 ports + * to complete link training. usually link trainig completes much faster + * so check status 10 times with 36ms sleep in places we need to wait for + * polling to complete. + */ +#define XHCI_PORT_POLLING_LFPS_TIME 36 diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 6f82d404883f..32d27134cc48 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -22,6 +22,9 @@ #include "xhci-ext-caps.h" #include "pci-quirks.h" +#include "xhci-port.h" +#include "xhci-caps.h" + /* max buffer size for trace and debug messages */ #define XHCI_MSG_MAX 500 @@ -62,90 +65,6 @@ struct xhci_cap_regs { /* Reserved up to (CAPLENGTH - 0x1C) */ }; -/* hc_capbase bitmasks */ -/* bits 7:0 - how long is the Capabilities register */ -#define HC_LENGTH(p) XHCI_HC_LENGTH(p) -/* bits 31:16 */ -#define HC_VERSION(p) (((p) >> 16) & 0xffff) - -/* HCSPARAMS1 - hcs_params1 - bitmasks */ -/* bits 0:7, Max Device Slots */ -#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) -#define HCS_SLOTS_MASK 0xff -/* bits 8:18, Max Interrupters */ -#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) -/* bits 24:31, Max Ports - max value is 0x7F = 127 ports */ -#define HCS_MAX_PORTS(p) (((p) >> 24) & 0x7f) - -/* HCSPARAMS2 - hcs_params2 - bitmasks */ -/* bits 0:3, frames or uframes that SW needs to queue transactions - * ahead of the HW to meet periodic deadlines */ -#define HCS_IST(p) (((p) >> 0) & 0xf) -/* bits 4:7, max number of Event Ring segments */ -#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) -/* bits 21:25 Hi 5 bits of Scratchpad buffers SW must allocate for the HW */ -/* bit 26 Scratchpad restore - for save/restore HW state - not used yet */ -/* bits 27:31 Lo 5 bits of Scratchpad buffers SW must allocate for the HW */ -#define HCS_MAX_SCRATCHPAD(p) ((((p) >> 16) & 0x3e0) | (((p) >> 27) & 0x1f)) - -/* HCSPARAMS3 - hcs_params3 - bitmasks */ -/* bits 0:7, Max U1 to U0 latency for the roothub ports */ -#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) -/* bits 16:31, Max U2 to U0 latency for the roothub ports */ -#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) - -/* HCCPARAMS - hcc_params - bitmasks */ -/* true: HC can use 64-bit address pointers */ -#define HCC_64BIT_ADDR(p) ((p) & (1 << 0)) -/* true: HC can do bandwidth negotiation */ -#define HCC_BANDWIDTH_NEG(p) ((p) & (1 << 1)) -/* true: HC uses 64-byte Device Context structures - * FIXME 64-byte context structures aren't supported yet. - */ -#define HCC_64BYTE_CONTEXT(p) ((p) & (1 << 2)) -/* true: HC has port power switches */ -#define HCC_PPC(p) ((p) & (1 << 3)) -/* true: HC has port indicators */ -#define HCS_INDICATOR(p) ((p) & (1 << 4)) -/* true: HC has Light HC Reset Capability */ -#define HCC_LIGHT_RESET(p) ((p) & (1 << 5)) -/* true: HC supports latency tolerance messaging */ -#define HCC_LTC(p) ((p) & (1 << 6)) -/* true: no secondary Stream ID Support */ -#define HCC_NSS(p) ((p) & (1 << 7)) -/* true: HC supports Stopped - Short Packet */ -#define HCC_SPC(p) ((p) & (1 << 9)) -/* true: HC has Contiguous Frame ID Capability */ -#define HCC_CFC(p) ((p) & (1 << 11)) -/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15 */ -#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) -/* Extended Capabilities pointer from PCI base - section 5.3.6 */ -#define HCC_EXT_CAPS(p) XHCI_HCC_EXT_CAPS(p) - -#define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32) - -/* db_off bitmask - bits 0:1 reserved */ -#define DBOFF_MASK (~0x3) - -/* run_regs_off bitmask - bits 0:4 reserved */ -#define RTSOFF_MASK (~0x1f) - -/* HCCPARAMS2 - hcc_params2 - bitmasks */ -/* true: HC supports U3 entry Capability */ -#define HCC2_U3C(p) ((p) & (1 << 0)) -/* true: HC supports Configure endpoint command Max exit latency too large */ -#define HCC2_CMC(p) ((p) & (1 << 1)) -/* true: HC supports Force Save context Capability */ -#define HCC2_FSC(p) ((p) & (1 << 2)) -/* true: HC supports Compliance Transition Capability */ -#define HCC2_CTC(p) ((p) & (1 << 3)) -/* true: HC support Large ESIT payload Capability > 48k */ -#define HCC2_LEC(p) ((p) & (1 << 4)) -/* true: HC support Configuration Information Capability */ -#define HCC2_CIC(p) ((p) & (1 << 5)) -/* true: HC support Extended TBC Capability, Isoc burst count > 65535 */ -#define HCC2_ETC(p) ((p) & (1 << 6)) - /* Number of registers per port */ #define NUM_PORT_REGS 4 @@ -291,181 +210,6 @@ struct xhci_op_regs { #define CONFIG_CIE (1 << 9) /* bits 10:31 - reserved and should be preserved */ -/* PORTSC - Port Status and Control Register - port_status_base bitmasks */ -/* true: device connected */ -#define PORT_CONNECT (1 << 0) -/* true: port enabled */ -#define PORT_PE (1 << 1) -/* bit 2 reserved and zeroed */ -/* true: port has an over-current condition */ -#define PORT_OC (1 << 3) -/* true: port reset signaling asserted */ -#define PORT_RESET (1 << 4) -/* Port Link State - bits 5:8 - * A read gives the current link PM state of the port, - * a write with Link State Write Strobe set sets the link state. - */ -#define PORT_PLS_MASK (0xf << 5) -#define XDEV_U0 (0x0 << 5) -#define XDEV_U1 (0x1 << 5) -#define XDEV_U2 (0x2 << 5) -#define XDEV_U3 (0x3 << 5) -#define XDEV_DISABLED (0x4 << 5) -#define XDEV_RXDETECT (0x5 << 5) -#define XDEV_INACTIVE (0x6 << 5) -#define XDEV_POLLING (0x7 << 5) -#define XDEV_RECOVERY (0x8 << 5) -#define XDEV_HOT_RESET (0x9 << 5) -#define XDEV_COMP_MODE (0xa << 5) -#define XDEV_TEST_MODE (0xb << 5) -#define XDEV_RESUME (0xf << 5) - -/* true: port has power (see HCC_PPC) */ -#define PORT_POWER (1 << 9) -/* bits 10:13 indicate device speed: - * 0 - undefined speed - port hasn't be initialized by a reset yet - * 1 - full speed - * 2 - low speed - * 3 - high speed - * 4 - super speed - * 5-15 reserved - */ -#define DEV_SPEED_MASK (0xf << 10) -#define XDEV_FS (0x1 << 10) -#define XDEV_LS (0x2 << 10) -#define XDEV_HS (0x3 << 10) -#define XDEV_SS (0x4 << 10) -#define XDEV_SSP (0x5 << 10) -#define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0<<10)) -#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) -#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_LS) -#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) -#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) -#define DEV_SUPERSPEEDPLUS(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP) -#define DEV_SUPERSPEED_ANY(p) (((p) & DEV_SPEED_MASK) >= XDEV_SS) -#define DEV_PORT_SPEED(p) (((p) >> 10) & 0x0f) - -/* Bits 20:23 in the Slot Context are the speed for the device */ -#define SLOT_SPEED_FS (XDEV_FS << 10) -#define SLOT_SPEED_LS (XDEV_LS << 10) -#define SLOT_SPEED_HS (XDEV_HS << 10) -#define SLOT_SPEED_SS (XDEV_SS << 10) -#define SLOT_SPEED_SSP (XDEV_SSP << 10) -/* Port Indicator Control */ -#define PORT_LED_OFF (0 << 14) -#define PORT_LED_AMBER (1 << 14) -#define PORT_LED_GREEN (2 << 14) -#define PORT_LED_MASK (3 << 14) -/* Port Link State Write Strobe - set this when changing link state */ -#define PORT_LINK_STROBE (1 << 16) -/* true: connect status change */ -#define PORT_CSC (1 << 17) -/* true: port enable change */ -#define PORT_PEC (1 << 18) -/* true: warm reset for a USB 3.0 device is done. A "hot" reset puts the port - * into an enabled state, and the device into the default state. A "warm" reset - * also resets the link, forcing the device through the link training sequence. - * SW can also look at the Port Reset register to see when warm reset is done. - */ -#define PORT_WRC (1 << 19) -/* true: over-current change */ -#define PORT_OCC (1 << 20) -/* true: reset change - 1 to 0 transition of PORT_RESET */ -#define PORT_RC (1 << 21) -/* port link status change - set on some port link state transitions: - * Transition Reason - * ------------------------------------------------------------------------------ - * - U3 to Resume Wakeup signaling from a device - * - Resume to Recovery to U0 USB 3.0 device resume - * - Resume to U0 USB 2.0 device resume - * - U3 to Recovery to U0 Software resume of USB 3.0 device complete - * - U3 to U0 Software resume of USB 2.0 device complete - * - U2 to U0 L1 resume of USB 2.1 device complete - * - U0 to U0 (???) L1 entry rejection by USB 2.1 device - * - U0 to disabled L1 entry error with USB 2.1 device - * - Any state to inactive Error on USB 3.0 port - */ -#define PORT_PLC (1 << 22) -/* port configure error change - port failed to configure its link partner */ -#define PORT_CEC (1 << 23) -#define PORT_CHANGE_MASK (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ - PORT_RC | PORT_PLC | PORT_CEC) - - -/* Cold Attach Status - xHC can set this bit to report device attached during - * Sx state. Warm port reset should be perfomed to clear this bit and move port - * to connected state. - */ -#define PORT_CAS (1 << 24) -/* wake on connect (enable) */ -#define PORT_WKCONN_E (1 << 25) -/* wake on disconnect (enable) */ -#define PORT_WKDISC_E (1 << 26) -/* wake on over-current (enable) */ -#define PORT_WKOC_E (1 << 27) -/* bits 28:29 reserved */ -/* true: device is non-removable - for USB 3.0 roothub emulation */ -#define PORT_DEV_REMOVE (1 << 30) -/* Initiate a warm port reset - complete when PORT_WRC is '1' */ -#define PORT_WR (1 << 31) - -/* We mark duplicate entries with -1 */ -#define DUPLICATE_ENTRY ((u8)(-1)) - -/* Port Power Management Status and Control - port_power_base bitmasks */ -/* Inactivity timer value for transitions into U1, in microseconds. - * Timeout can be up to 127us. 0xFF means an infinite timeout. - */ -#define PORT_U1_TIMEOUT(p) ((p) & 0xff) -#define PORT_U1_TIMEOUT_MASK 0xff -/* Inactivity timer value for transitions into U2 */ -#define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) -#define PORT_U2_TIMEOUT_MASK (0xff << 8) -/* Bits 24:31 for port testing */ - -/* USB2 Protocol PORTSPMSC */ -#define PORT_L1S_MASK 7 -#define PORT_L1S_SUCCESS 1 -#define PORT_RWE (1 << 3) -#define PORT_HIRD(p) (((p) & 0xf) << 4) -#define PORT_HIRD_MASK (0xf << 4) -#define PORT_L1DS_MASK (0xff << 8) -#define PORT_L1DS(p) (((p) & 0xff) << 8) -#define PORT_HLE (1 << 16) -#define PORT_TEST_MODE_SHIFT 28 - -/* USB3 Protocol PORTLI Port Link Information */ -#define PORT_RX_LANES(p) (((p) >> 16) & 0xf) -#define PORT_TX_LANES(p) (((p) >> 20) & 0xf) - -/* USB2 Protocol PORTHLPMC */ -#define PORT_HIRDM(p)((p) & 3) -#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2) -#define PORT_BESLD(p)(((p) & 0xf) << 10) - -/* use 512 microseconds as USB2 LPM L1 default timeout. */ -#define XHCI_L1_TIMEOUT 512 - -/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency. - * Safe to use with mixed HIRD and BESL systems (host and device) and is used - * by other operating systems. - * - * XHCI 1.0 errata 8/14/12 Table 13 notes: - * "Software should choose xHC BESL/BESLD field values that do not violate a - * device's resume latency requirements, - * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached, - * or not program values < '4' if BLC = '0' and a BESL device is attached. - */ -#define XHCI_DEFAULT_BESL 4 - -/* - * USB3 specification define a 360ms tPollingLFPSTiemout for USB3 ports - * to complete link training. usually link trainig completes much faster - * so check status 10 times with 36ms sleep in places we need to wait for - * polling to complete. - */ -#define XHCI_PORT_POLLING_LFPS_TIME 36 - /** * struct xhci_intr_reg - Interrupt Register Set * @irq_pending: IMAN - Interrupt Management Register. Used to enable diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c index 9f2be22af844..7c657ea2dabd 100644 --- a/drivers/usb/mtu3/mtu3_host.c +++ b/drivers/usb/mtu3/mtu3_host.c @@ -34,6 +34,18 @@ #define WC0_SSUSB0_CDEN BIT(6) #define WC0_IS_SPM_EN BIT(1) +/* mt8195 */ +#define PERI_WK_CTRL0_8195 0x04 +#define WC0_IS_P_95 BIT(30) /* polarity */ +#define WC0_IS_C_95(x) ((u32)(((x) & 0x7) << 27)) +#define WC0_IS_EN_P3_95 BIT(26) +#define WC0_IS_EN_P2_95 BIT(25) + +#define PERI_WK_CTRL1_8195 0x20 +#define WC1_IS_C_95(x) ((u32)(((x) & 0xf) << 28)) +#define WC1_IS_P_95 BIT(12) +#define WC1_IS_EN_P0_95 BIT(6) + /* mt2712 etc */ #define PERI_SSUSB_SPM_CTRL 0x0 #define SSC_IP_SLEEP_EN BIT(4) @@ -44,6 +56,9 @@ enum ssusb_uwk_vers { SSUSB_UWK_V2, SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */ SSUSB_UWK_V1_2, /* specific revision 1.02 */ + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ }; /* @@ -70,6 +85,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN; val = enable ? msk : 0; break; + case SSUSB_UWK_V1_3: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8195; + msk = WC1_IS_EN_P0_95 | WC1_IS_C_95(0xf) | WC1_IS_P_95; + val = enable ? (WC1_IS_EN_P0_95 | WC1_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_5: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P2_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P2_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_6: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; case SSUSB_UWK_V2: reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index 9ab50f26db60..8f735a86cd19 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -74,33 +74,26 @@ static void nop_reset(struct usb_phy_generic *nop) } /* interface to regulator framework */ -static void nop_set_vbus_draw(struct usb_phy_generic *nop, unsigned mA) +static int nop_set_vbus(struct usb_otg *otg, bool enable) { - struct regulator *vbus_draw = nop->vbus_draw; - int enabled; - int ret; + int ret = 0; + struct usb_phy_generic *nop = dev_get_drvdata(otg->usb_phy->dev); - if (!vbus_draw) - return; + if (!nop->vbus_draw) + return 0; - enabled = nop->vbus_draw_enabled; - if (mA) { - regulator_set_current_limit(vbus_draw, 0, 1000 * mA); - if (!enabled) { - ret = regulator_enable(vbus_draw); - if (ret < 0) - return; - nop->vbus_draw_enabled = 1; - } - } else { - if (enabled) { - ret = regulator_disable(vbus_draw); - if (ret < 0) - return; - nop->vbus_draw_enabled = 0; - } + if (enable && !nop->vbus_draw_enabled) { + ret = regulator_enable(nop->vbus_draw); + if (ret) + nop->vbus_draw_enabled = false; + else + nop->vbus_draw_enabled = true; + + } else if (!enable && nop->vbus_draw_enabled) { + ret = regulator_disable(nop->vbus_draw); + nop->vbus_draw_enabled = false; } - nop->mA = mA; + return ret; } @@ -120,14 +113,9 @@ static irqreturn_t nop_gpio_vbus_thread(int irq, void *data) otg->state = OTG_STATE_B_PERIPHERAL; nop->phy.last_event = status; - /* drawing a "unit load" is *always* OK, except for OTG */ - nop_set_vbus_draw(nop, 100); - atomic_notifier_call_chain(&nop->phy.notifier, status, otg->gadget); } else { - nop_set_vbus_draw(nop, 0); - status = USB_EVENT_NONE; otg->state = OTG_STATE_B_IDLE; nop->phy.last_event = status; @@ -274,6 +262,13 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop) return dev_err_probe(dev, PTR_ERR(nop->vbus_draw), "could not get vbus regulator\n"); + nop->vbus_draw = devm_regulator_get_exclusive(dev, "vbus"); + if (PTR_ERR(nop->vbus_draw) == -ENODEV) + nop->vbus_draw = NULL; + if (IS_ERR(nop->vbus_draw)) + return dev_err_probe(dev, PTR_ERR(nop->vbus_draw), + "could not get vbus regulator\n"); + nop->dev = dev; nop->phy.dev = nop->dev; nop->phy.label = "nop-xceiv"; @@ -284,6 +279,7 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop) nop->phy.otg->usb_phy = &nop->phy; nop->phy.otg->set_host = nop_set_host; nop->phy.otg->set_peripheral = nop_set_peripheral; + nop->phy.otg->set_vbus = nop_set_vbus; return 0; } @@ -341,6 +337,9 @@ static void usb_phy_generic_remove(struct platform_device *pdev) struct usb_phy_generic *nop = platform_get_drvdata(pdev); usb_remove_phy(&nop->phy); + + if (nop->vbus_draw && nop->vbus_draw_enabled) + regulator_disable(nop->vbus_draw); } static const struct of_device_id nop_xceiv_dt_ids[] = { diff --git a/drivers/usb/storage/sddr55.c b/drivers/usb/storage/sddr55.c index 15dc25801cdc..0aa079405d23 100644 --- a/drivers/usb/storage/sddr55.c +++ b/drivers/usb/storage/sddr55.c @@ -196,7 +196,7 @@ static int sddr55_read_data(struct us_data *us, unsigned char *buffer; unsigned int pba; - unsigned long address; + unsigned int address; unsigned short pages; unsigned int len, offset; @@ -316,7 +316,7 @@ static int sddr55_write_data(struct us_data *us, unsigned int pba; unsigned int new_pba; - unsigned long address; + unsigned int address; unsigned short pages; int i; diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index f81bec0c7b86..5a80776c7255 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -50,13 +50,17 @@ enum { enum dp_state { DP_STATE_IDLE, DP_STATE_ENTER, + DP_STATE_ENTER_PRIME, DP_STATE_UPDATE, DP_STATE_CONFIGURE, + DP_STATE_CONFIGURE_PRIME, DP_STATE_EXIT, + DP_STATE_EXIT_PRIME, }; struct dp_altmode { struct typec_displayport_data data; + struct typec_displayport_data data_prime; enum dp_state state; bool hpd; @@ -67,6 +71,7 @@ struct dp_altmode { struct typec_altmode *alt; const struct typec_altmode *port; struct fwnode_handle *connector_fwnode; + struct typec_altmode *plug_prime; }; static int dp_altmode_notify(struct dp_altmode *dp) @@ -99,12 +104,18 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con) conf |= DP_CONF_UFP_U_AS_DFP_D; pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) & DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo); + /* Account for active cable capabilities */ + if (dp->plug_prime) + pin_assign &= DP_CAP_DFP_D_PIN_ASSIGN(dp->plug_prime->vdo); break; case DP_STATUS_CON_UFP_D: case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */ conf |= DP_CONF_UFP_U_AS_UFP_D; pin_assign = DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo) & DP_CAP_PIN_ASSIGN_DFP_D(dp->port->vdo); + /* Account for active cable capabilities */ + if (dp->plug_prime) + pin_assign &= DP_CAP_UFP_D_PIN_ASSIGN(dp->plug_prime->vdo); break; default: break; @@ -130,6 +141,8 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con) } dp->data.conf = conf; + if (dp->plug_prime) + dp->data_prime.conf = conf; return 0; } @@ -143,7 +156,9 @@ static int dp_altmode_status_update(struct dp_altmode *dp) if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) { dp->data.conf = 0; - dp->state = DP_STATE_CONFIGURE; + dp->data_prime.conf = 0; + dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : + DP_STATE_CONFIGURE; } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) { dp->state = DP_STATE_EXIT; } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) { @@ -209,6 +224,19 @@ static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf) return ret; } +static int dp_altmode_configure_vdm_cable(struct dp_altmode *dp, u32 conf) +{ + int svdm_version = typec_altmode_get_cable_svdm_version(dp->plug_prime); + u32 header; + + if (svdm_version < 0) + return svdm_version; + + header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE); + + return typec_cable_altmode_vdm(dp->plug_prime, TYPEC_PLUG_SOP_P, header, &conf, 2); +} + static void dp_altmode_work(struct work_struct *work) { struct dp_altmode *dp = container_of(work, struct dp_altmode, work); @@ -225,6 +253,19 @@ static void dp_altmode_work(struct work_struct *work) if (ret && ret != -EBUSY) dev_err(&dp->alt->dev, "failed to enter mode\n"); break; + case DP_STATE_ENTER_PRIME: + ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL); + /* + * If we fail to enter Alt Mode on SOP', then we should drop the + * plug from the driver and attempt to run the driver without + * it. + */ + if (ret && ret != -EBUSY) { + dev_err(&dp->alt->dev, "plug failed to enter mode\n"); + dp->state = DP_STATE_ENTER; + goto disable_prime; + } + break; case DP_STATE_UPDATE: svdm_version = typec_altmode_get_svdm_version(dp->alt); if (svdm_version < 0) @@ -243,10 +284,24 @@ static void dp_altmode_work(struct work_struct *work) dev_err(&dp->alt->dev, "unable to send Configure command (%d)\n", ret); break; + case DP_STATE_CONFIGURE_PRIME: + ret = dp_altmode_configure_vdm_cable(dp, dp->data_prime.conf); + if (ret) { + dev_err(&dp->plug_prime->dev, + "unable to send Configure command (%d)\n", + ret); + dp->state = DP_STATE_CONFIGURE; + goto disable_prime; + } + break; case DP_STATE_EXIT: if (typec_altmode_exit(dp->alt)) dev_err(&dp->alt->dev, "Exit Mode Failed!\n"); break; + case DP_STATE_EXIT_PRIME: + if (typec_cable_altmode_exit(dp->plug_prime, TYPEC_PLUG_SOP_P)) + dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n"); + break; default: break; } @@ -254,6 +309,13 @@ static void dp_altmode_work(struct work_struct *work) dp->state = DP_STATE_IDLE; mutex_unlock(&dp->lock); + return; + +disable_prime: + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + schedule_work(&dp->work); + mutex_unlock(&dp->lock); } static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo) @@ -314,6 +376,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt, dp->hpd = false; sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); } + if (dp->plug_prime) + dp->state = DP_STATE_EXIT_PRIME; break; case DP_CMD_STATUS_UPDATE: dp->data.status = *vdo; @@ -348,10 +412,84 @@ err_unlock: return ret; } +static int dp_cable_altmode_vdm(struct typec_altmode *alt, enum typec_plug_index sop, + const u32 hdr, const u32 *vdo, int count) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + int cmd_type = PD_VDO_CMDT(hdr); + int cmd = PD_VDO_CMD(hdr); + int ret = 0; + + mutex_lock(&dp->lock); + + if (dp->state != DP_STATE_IDLE) { + ret = -EBUSY; + goto err_unlock; + } + + switch (cmd_type) { + case CMDT_RSP_ACK: + switch (cmd) { + case CMD_ENTER_MODE: + typec_altmode_update_active(dp->plug_prime, true); + dp->state = DP_STATE_ENTER; + break; + case CMD_EXIT_MODE: + dp->data_prime.status = 0; + dp->data_prime.conf = 0; + typec_altmode_update_active(dp->plug_prime, false); + break; + case DP_CMD_CONFIGURE: + dp->state = DP_STATE_CONFIGURE; + break; + default: + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case DP_CMD_CONFIGURE: + dp->data_prime.conf = 0; + /* Attempt to configure on SOP, drop plug */ + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + dp->state = DP_STATE_CONFIGURE; + break; + default: + break; + } + break; + default: + break; + } + + if (dp->state != DP_STATE_IDLE) + schedule_work(&dp->work); + +err_unlock: + mutex_unlock(&dp->lock); + return ret; +} + static int dp_altmode_activate(struct typec_altmode *alt, int activate) { - return activate ? typec_altmode_enter(alt, NULL) : - typec_altmode_exit(alt); + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + int ret; + + if (activate) { + if (dp->plug_prime) { + ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL); + if (ret < 0) { + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + } else { + return ret; + } + } + return typec_altmode_enter(alt, NULL); + } else { + return typec_altmode_exit(alt); + } } static const struct typec_altmode_ops dp_altmode_ops = { @@ -360,6 +498,10 @@ static const struct typec_altmode_ops dp_altmode_ops = { .activate = dp_altmode_activate, }; +static const struct typec_cable_ops dp_cable_ops = { + .vdm = dp_cable_altmode_vdm, +}; + static const char * const configurations[] = { [DP_CONF_USB] = "USB", [DP_CONF_DFP_D] = "source", @@ -501,6 +643,7 @@ pin_assignment_store(struct device *dev, struct device_attribute *attr, /* Only send Configure command if a configuration has been set */ if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) { + /* todo: send manual configure over SOP'*/ ret = dp_altmode_configure_vdm(dp, conf); if (ret) goto out_unlock; @@ -574,6 +717,7 @@ static const struct attribute_group dp_altmode_group = { int dp_altmode_probe(struct typec_altmode *alt) { const struct typec_altmode *port = typec_altmode_get_partner(alt); + struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); struct fwnode_handle *fwnode; struct dp_altmode *dp; int ret; @@ -603,6 +747,13 @@ int dp_altmode_probe(struct typec_altmode *alt) alt->desc = "DisplayPort"; alt->ops = &dp_altmode_ops; + if (plug) { + plug->desc = "Displayport"; + plug->cable_ops = &dp_cable_ops; + } + + dp->plug_prime = plug; + fwnode = dev_fwnode(alt->dev.parent->parent); /* typec_port fwnode */ if (fwnode_property_present(fwnode, "displayport")) dp->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); @@ -612,8 +763,10 @@ int dp_altmode_probe(struct typec_altmode *alt) dp->connector_fwnode = NULL; typec_altmode_set_drvdata(alt, dp); + if (plug) + typec_altmode_set_drvdata(plug, dp); - dp->state = DP_STATE_ENTER; + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; schedule_work(&dp->work); return 0; @@ -626,6 +779,7 @@ void dp_altmode_remove(struct typec_altmode *alt) sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group); cancel_work_sync(&dp->work); + typec_altmode_put_plug(dp->plug_prime); if (dp->connector_fwnode) { drm_connector_oob_hotplug_event(dp->connector_fwnode, diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index e95ec7e382bb..6ea103e1abae 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -245,6 +245,108 @@ typec_altmode_get_partner(struct typec_altmode *adev) EXPORT_SYMBOL_GPL(typec_altmode_get_partner); /* -------------------------------------------------------------------------- */ +/* API for cable alternate modes */ + +/** + * typec_cable_altmode_enter - Enter Mode + * @adev: The alternate mode + * @sop: Cable plug target for Enter Mode command + * @vdo: VDO for the Enter Mode command + * + * Alternate mode drivers use this function to enter mode on the cable plug. + * If the alternate mode does not require VDO, @vdo must be NULL. + */ +int typec_cable_altmode_enter(struct typec_altmode *adev, enum typec_plug_index sop, u32 *vdo) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev; + + if (!adev || adev->active) + return 0; + + if (!partner) + return -ENODEV; + + pdev = &partner->adev; + + if (!pdev->active) + return -EPERM; + + if (!pdev->cable_ops || !pdev->cable_ops->enter) + return -EOPNOTSUPP; + + return pdev->cable_ops->enter(pdev, sop, vdo); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_enter); + +/** + * typec_cable_altmode_exit - Exit Mode + * @adev: The alternate mode + * @sop: Cable plug target for Exit Mode command + * + * The alternate mode drivers use this function to exit mode on the cable plug. + */ +int typec_cable_altmode_exit(struct typec_altmode *adev, enum typec_plug_index sop) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev; + + if (!adev || !adev->active) + return 0; + + if (!partner) + return -ENODEV; + + pdev = &partner->adev; + + if (!pdev->cable_ops || !pdev->cable_ops->exit) + return -EOPNOTSUPP; + + return pdev->cable_ops->exit(pdev, sop); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_exit); + +/** + * typec_cable_altmode_vdm - Send Vendor Defined Messages (VDM) between the cable plug and port. + * @adev: Alternate mode handle + * @sop: Cable plug target for VDM + * @header: VDM Header + * @vdo: Array of Vendor Defined Data Objects + * @count: Number of Data Objects + * + * The alternate mode drivers use this function for SVID specific communication + * with the cable plugs. The port drivers use it to deliver the Structured VDMs + * received from the cable plugs to the alternate mode drivers. + */ +int typec_cable_altmode_vdm(struct typec_altmode *adev, enum typec_plug_index sop, + const u32 header, const u32 *vdo, int count) +{ + struct altmode *altmode; + struct typec_altmode *pdev; + + if (!adev) + return 0; + + altmode = to_altmode(adev); + + if (is_typec_plug(adev->dev.parent)) { + if (!altmode->partner) + return -ENODEV; + pdev = &altmode->partner->adev; + } else { + if (!altmode->plug[sop]) + return -ENODEV; + pdev = &altmode->plug[sop]->adev; + } + + if (!pdev->cable_ops || !pdev->cable_ops->vdm) + return -EOPNOTSUPP; + + return pdev->cable_ops->vdm(pdev, sop, header, vdo, count); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_vdm); + +/* -------------------------------------------------------------------------- */ /* API for the alternate mode drivers */ /** diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 015aa9253353..caea2b829980 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -2132,6 +2132,46 @@ int typec_get_negotiated_svdm_version(struct typec_port *port) EXPORT_SYMBOL_GPL(typec_get_negotiated_svdm_version); /** + * typec_get_cable_svdm_version - Get cable negotiated SVDM Version + * @port: USB Type-C Port. + * + * Get the negotiated SVDM Version for the cable. The Version is set to the port + * default value based on the PD Revision during cable registration, and updated + * after a successful Discover Identity if the negotiated value is less than the + * default. + * + * Returns usb_pd_svdm_ver if the cable has been registered otherwise -ENODEV. + */ +int typec_get_cable_svdm_version(struct typec_port *port) +{ + enum usb_pd_svdm_ver svdm_version; + struct device *cable_dev; + + cable_dev = device_find_child(&port->dev, NULL, cable_match); + if (!cable_dev) + return -ENODEV; + + svdm_version = to_typec_cable(cable_dev)->svdm_version; + put_device(cable_dev); + + return svdm_version; +} +EXPORT_SYMBOL_GPL(typec_get_cable_svdm_version); + +/** + * typec_cable_set_svdm_version - Set negotiated Structured VDM (SVDM) Version + * @cable: USB Type-C Active Cable that supports SVDM + * @svdm_version: Negotiated SVDM Version + * + * This routine is used to save the negotiated SVDM Version. + */ +void typec_cable_set_svdm_version(struct typec_cable *cable, enum usb_pd_svdm_ver svdm_version) +{ + cable->svdm_version = svdm_version; +} +EXPORT_SYMBOL_GPL(typec_cable_set_svdm_version); + +/** * typec_get_drvdata - Return private driver data pointer * @port: USB Type-C port */ @@ -2281,6 +2321,25 @@ void typec_port_register_altmodes(struct typec_port *port, EXPORT_SYMBOL_GPL(typec_port_register_altmodes); /** + * typec_port_register_cable_ops - Register typec_cable_ops to port altmodes + * @altmodes: USB Type-C Port's altmode vector + * @max_altmodes: The maximum number of alt modes supported by the port + * @ops: Cable alternate mode vector + */ +void typec_port_register_cable_ops(struct typec_altmode **altmodes, int max_altmodes, + const struct typec_cable_ops *ops) +{ + int i; + + for (i = 0; i < max_altmodes; i++) { + if (!altmodes[i]) + return; + altmodes[i]->cable_ops = ops; + } +} +EXPORT_SYMBOL_GPL(typec_port_register_cable_ops); + +/** * typec_register_port - Register a USB Type-C Port * @parent: Parent device * @cap: Description of the port diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h index c36761ba3f59..759b98355eeb 100644 --- a/drivers/usb/typec/class.h +++ b/drivers/usb/typec/class.h @@ -23,6 +23,7 @@ struct typec_cable { struct usb_pd_identity *identity; unsigned int active:1; u16 pd_revision; /* 0300H = "3.0" */ + enum usb_pd_svdm_ver svdm_version; }; struct typec_partner { diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index d2cb5e733e57..399c7b0983df 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -36,6 +36,16 @@ config TYPEC_MUX_INTEL_PMC control the USB role switch and also the multiplexer/demultiplexer switches used with USB Type-C Alternate Modes. +config TYPEC_MUX_IT5205 + tristate "ITE IT5205 Type-C USB Alt Mode Passive MUX driver" + depends on I2C + select REGMAP_I2C + help + Driver for the ITE IT5205 Type-C USB Alternate Mode Passive MUX + which provides support for muxing DisplayPort and sideband signals + on a common USB Type-C connector. + If compiled as a module, the module will be named it5205. + config TYPEC_MUX_NB7VPQ904M tristate "On Semiconductor NB7VPQ904M Type-C redriver driver" depends on I2C diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index 57dc9ac6f8dc..bb96f30267af 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_TYPEC_MUX_FSA4480) += fsa4480.o obj-$(CONFIG_TYPEC_MUX_GPIO_SBU) += gpio-sbu-mux.o obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o +obj-$(CONFIG_TYPEC_MUX_IT5205) += it5205.o obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M) += nb7vpq904m.o obj-$(CONFIG_TYPEC_MUX_PTN36502) += ptn36502.o obj-$(CONFIG_TYPEC_MUX_WCD939X_USBSS) += wcd939x-usbss.o diff --git a/drivers/usb/typec/mux/it5205.c b/drivers/usb/typec/mux/it5205.c new file mode 100644 index 000000000000..5535932e42cd --- /dev/null +++ b/drivers/usb/typec/mux/it5205.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ITE IT5205 Type-C USB alternate mode passive mux + * + * Copyright (c) 2020 MediaTek Inc. + * Copyright (c) 2024 Collabora Ltd. + * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> + * + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/typec.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> + +#define IT5205_REG_CHIP_ID(x) (0x4 + (x)) +#define IT5205FN_CHIP_ID 0x35323035 /* "5205" */ + +/* MUX power down register */ +#define IT5205_REG_MUXPDR 0x10 +#define IT5205_MUX_POWER_DOWN BIT(0) + +/* MUX control register */ +#define IT5205_REG_MUXCR 0x11 +#define IT5205_POLARITY_INVERTED BIT(4) +#define IT5205_DP_USB_CTRL_MASK GENMASK(3, 0) +#define IT5205_DP 0x0f +#define IT5205_DP_USB 0x03 +#define IT5205_USB 0x07 + +/* Vref Select Register */ +#define IT5205_REG_VSR 0x10 +#define IT5205_VREF_SELECT_MASK GENMASK(5, 4) +#define IT5205_VREF_SELECT_3_3V 0x00 +#define IT5205_VREF_SELECT_OFF 0x20 + +/* CSBU Over Voltage Protection Register */ +#define IT5205_REG_CSBUOVPSR 0x1e +#define IT5205_OVP_SELECT_MASK GENMASK(5, 4) +#define IT5205_OVP_3_90V 0x00 +#define IT5205_OVP_3_68V 0x10 +#define IT5205_OVP_3_62V 0x20 +#define IT5205_OVP_3_57V 0x30 + +/* CSBU Switch Register */ +#define IT5205_REG_CSBUSR 0x22 +#define IT5205_CSBUSR_SWITCH BIT(0) + +/* Interrupt Switch Register */ +#define IT5205_REG_ISR 0x25 +#define IT5205_ISR_CSBU_MASK BIT(4) +#define IT5205_ISR_CSBU_OVP BIT(0) + +struct it5205 { + struct i2c_client *client; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; +}; + +static int it5205_switch_set(struct typec_switch_dev *sw, enum typec_orientation orientation) +{ + struct it5205 *it = typec_switch_get_drvdata(sw); + + switch (orientation) { + case TYPEC_ORIENTATION_NORMAL: + regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_POLARITY_INVERTED, 0); + break; + case TYPEC_ORIENTATION_REVERSE: + regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_POLARITY_INVERTED, IT5205_POLARITY_INVERTED); + break; + case TYPEC_ORIENTATION_NONE: + fallthrough; + default: + regmap_write(it->regmap, IT5205_REG_MUXCR, 0); + break; + } + + return 0; +} + +static int it5205_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct it5205 *it = typec_mux_get_drvdata(mux); + u8 val; + + if (state->mode >= TYPEC_STATE_MODAL && + state->alt->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + switch (state->mode) { + case TYPEC_STATE_USB: + val = IT5205_USB; + break; + case TYPEC_DP_STATE_C: + fallthrough; + case TYPEC_DP_STATE_E: + val = IT5205_DP; + break; + case TYPEC_DP_STATE_D: + val = IT5205_DP_USB; + break; + case TYPEC_STATE_SAFE: + fallthrough; + default: + val = 0; + break; + } + + return regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_DP_USB_CTRL_MASK, val); +} + +static irqreturn_t it5205_irq_handler(int irq, void *data) +{ + struct it5205 *it = data; + int ret; + u32 val; + + ret = regmap_read(it->regmap, IT5205_REG_ISR, &val); + if (ret) + return IRQ_NONE; + + if (val & IT5205_ISR_CSBU_OVP) { + dev_warn(&it->client->dev, "Overvoltage detected!\n"); + + /* Reset CSBU */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, 0); + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, IT5205_CSBUSR_SWITCH); + } + + return IRQ_HANDLED; +} + +static void it5205_enable_ovp(struct it5205 *it) +{ + /* Select Vref 3.3v */ + regmap_update_bits(it->regmap, IT5205_REG_VSR, + IT5205_VREF_SELECT_MASK, IT5205_VREF_SELECT_3_3V); + + /* Trigger OVP at 3.68V */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUOVPSR, + IT5205_OVP_SELECT_MASK, IT5205_OVP_3_68V); + + /* Unmask OVP interrupt */ + regmap_update_bits(it->regmap, IT5205_REG_ISR, + IT5205_ISR_CSBU_MASK, 0); + + /* Enable CSBU Interrupt */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, IT5205_CSBUSR_SWITCH); +} + +static const struct regmap_config it5205_regmap = { + .max_register = 0x2f, + .reg_bits = 8, + .val_bits = 8, +}; + +static int it5205_probe(struct i2c_client *client) +{ + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct device *dev = &client->dev; + struct it5205 *it; + u32 val, chipid = 0; + int i, ret; + + it = devm_kzalloc(dev, sizeof(*it), GFP_KERNEL); + if (!it) + return -ENOMEM; + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + it->client = client; + + it->regmap = devm_regmap_init_i2c(client, &it5205_regmap); + if (IS_ERR(it->regmap)) + return dev_err_probe(dev, PTR_ERR(it->regmap), + "Failed to init regmap\n"); + + /* IT5205 needs a long time to power up after enabling regulator */ + msleep(50); + + /* Unset poweroff bit */ + ret = regmap_write(it->regmap, IT5205_REG_MUXPDR, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to set power on\n"); + + /* Read the 32 bits ChipID */ + for (i = 3; i >= 0; i--) { + ret = regmap_read(it->regmap, IT5205_REG_CHIP_ID(i), &val); + if (ret) + return ret; + + chipid |= val << (i * 8); + } + + if (chipid != IT5205FN_CHIP_ID) + return dev_err_probe(dev, -EINVAL, + "Unknown ChipID 0x%x\n", chipid); + + /* Initialize as USB mode with default (non-inverted) polarity */ + ret = regmap_write(it->regmap, IT5205_REG_MUXCR, IT5205_USB); + if (ret) + return dev_err_probe(dev, ret, "Cannot set mode to USB\n"); + + sw_desc.drvdata = it; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = it5205_switch_set; + + it->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(it->sw)) + return dev_err_probe(dev, PTR_ERR(it->sw), + "failed to register typec switch\n"); + + mux_desc.drvdata = it; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = it5205_mux_set; + + it->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(it->mux)) { + typec_switch_unregister(it->sw); + return dev_err_probe(dev, PTR_ERR(it->mux), + "failed to register typec mux\n"); + } + + i2c_set_clientdata(client, it); + + if (of_property_read_bool(dev->of_node, "ite,ovp-enable") && client->irq) { + it5205_enable_ovp(it); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + it5205_irq_handler, + IRQF_ONESHOT, dev_name(dev), it); + if (ret) { + typec_mux_unregister(it->mux); + typec_switch_unregister(it->sw); + return dev_err_probe(dev, ret, "Failed to request irq\n"); + } + } + + return 0; +} + +static void it5205_remove(struct i2c_client *client) +{ + struct it5205 *it = i2c_get_clientdata(client); + + typec_mux_unregister(it->mux); + typec_switch_unregister(it->sw); +} + +static const struct i2c_device_id it5205_table[] = { + { "it5205" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, it5205_table); + +static const struct of_device_id it5205_of_table[] = { + { .compatible = "ite,it5205" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, it5205_of_table); + +static struct i2c_driver it5205_driver = { + .driver = { + .name = "it5205", + .of_match_table = it5205_of_table, + }, + .probe = it5205_probe, + .remove = it5205_remove, + .id_table = it5205_table, +}; +module_i2c_driver(it5205_driver); + +MODULE_AUTHOR("Tianping Fang <tianping.fang@mediatek.com>"); +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); +MODULE_DESCRIPTION("ITE IT5205 alternate mode passive MUX driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index bc21006e979c..ef18a448b740 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -1467,7 +1467,7 @@ static int fusb302_pd_read_message(struct fusb302_chip *chip, if ((!len) && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC)) tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); else - tcpm_pd_receive(chip->tcpm_port, msg); + tcpm_pd_receive(chip->tcpm_port, msg, TCPC_TX_SOP); return ret; } diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c index 1a2b4bddaa97..82e3f59ea471 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c @@ -20,130 +20,15 @@ #include <drm/bridge/aux-bridge.h> +#include "qcom_pmic_typec.h" #include "qcom_pmic_typec_pdphy.h" #include "qcom_pmic_typec_port.h" struct pmic_typec_resources { - struct pmic_typec_pdphy_resources *pdphy_res; - struct pmic_typec_port_resources *port_res; + const struct pmic_typec_pdphy_resources *pdphy_res; + const struct pmic_typec_port_resources *port_res; }; -struct pmic_typec { - struct device *dev; - struct tcpm_port *tcpm_port; - struct tcpc_dev tcpc; - struct pmic_typec_pdphy *pmic_typec_pdphy; - struct pmic_typec_port *pmic_typec_port; - bool vbus_enabled; - struct mutex lock; /* VBUS state serialization */ -}; - -#define tcpc_to_tcpm(_tcpc_) container_of(_tcpc_, struct pmic_typec, tcpc) - -static int qcom_pmic_typec_get_vbus(struct tcpc_dev *tcpc) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - int ret; - - mutex_lock(&tcpm->lock); - ret = tcpm->vbus_enabled || qcom_pmic_typec_port_get_vbus(tcpm->pmic_typec_port); - mutex_unlock(&tcpm->lock); - - return ret; -} - -static int qcom_pmic_typec_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - int ret = 0; - - mutex_lock(&tcpm->lock); - if (tcpm->vbus_enabled == on) - goto done; - - ret = qcom_pmic_typec_port_set_vbus(tcpm->pmic_typec_port, on); - if (ret) - goto done; - - tcpm->vbus_enabled = on; - tcpm_vbus_change(tcpm->tcpm_port); - -done: - dev_dbg(tcpm->dev, "set_vbus set: %d result %d\n", on, ret); - mutex_unlock(&tcpm->lock); - - return ret; -} - -static int qcom_pmic_typec_set_vconn(struct tcpc_dev *tcpc, bool on) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_port_set_vconn(tcpm->pmic_typec_port, on); -} - -static int qcom_pmic_typec_get_cc(struct tcpc_dev *tcpc, - enum typec_cc_status *cc1, - enum typec_cc_status *cc2) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_port_get_cc(tcpm->pmic_typec_port, cc1, cc2); -} - -static int qcom_pmic_typec_set_cc(struct tcpc_dev *tcpc, - enum typec_cc_status cc) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_port_set_cc(tcpm->pmic_typec_port, cc); -} - -static int qcom_pmic_typec_set_polarity(struct tcpc_dev *tcpc, - enum typec_cc_polarity pol) -{ - /* Polarity is set separately by phy-qcom-qmp.c */ - return 0; -} - -static int qcom_pmic_typec_start_toggling(struct tcpc_dev *tcpc, - enum typec_port_type port_type, - enum typec_cc_status cc) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_port_start_toggling(tcpm->pmic_typec_port, - port_type, cc); -} - -static int qcom_pmic_typec_set_roles(struct tcpc_dev *tcpc, bool attached, - enum typec_role power_role, - enum typec_data_role data_role) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_pdphy_set_roles(tcpm->pmic_typec_pdphy, - data_role, power_role); -} - -static int qcom_pmic_typec_set_pd_rx(struct tcpc_dev *tcpc, bool on) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_pdphy_set_pd_rx(tcpm->pmic_typec_pdphy, on); -} - -static int qcom_pmic_typec_pd_transmit(struct tcpc_dev *tcpc, - enum tcpm_transmit_type type, - const struct pd_message *msg, - unsigned int negotiated_rev) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_pdphy_pd_transmit(tcpm->pmic_typec_pdphy, type, - msg, negotiated_rev); -} - static int qcom_pmic_typec_init(struct tcpc_dev *tcpc) { return 0; @@ -170,16 +55,6 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) tcpm->dev = dev; tcpm->tcpc.init = qcom_pmic_typec_init; - tcpm->tcpc.get_vbus = qcom_pmic_typec_get_vbus; - tcpm->tcpc.set_vbus = qcom_pmic_typec_set_vbus; - tcpm->tcpc.set_cc = qcom_pmic_typec_set_cc; - tcpm->tcpc.get_cc = qcom_pmic_typec_get_cc; - tcpm->tcpc.set_polarity = qcom_pmic_typec_set_polarity; - tcpm->tcpc.set_vconn = qcom_pmic_typec_set_vconn; - tcpm->tcpc.start_toggling = qcom_pmic_typec_start_toggling; - tcpm->tcpc.set_pd_rx = qcom_pmic_typec_set_pd_rx; - tcpm->tcpc.set_roles = qcom_pmic_typec_set_roles; - tcpm->tcpc.pd_transmit = qcom_pmic_typec_pd_transmit; regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) { @@ -191,25 +66,16 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) if (ret) return ret; - tcpm->pmic_typec_port = qcom_pmic_typec_port_alloc(dev); - if (IS_ERR(tcpm->pmic_typec_port)) - return PTR_ERR(tcpm->pmic_typec_port); - - tcpm->pmic_typec_pdphy = qcom_pmic_typec_pdphy_alloc(dev); - if (IS_ERR(tcpm->pmic_typec_pdphy)) - return PTR_ERR(tcpm->pmic_typec_pdphy); - - ret = qcom_pmic_typec_port_probe(pdev, tcpm->pmic_typec_port, + ret = qcom_pmic_typec_port_probe(pdev, tcpm, res->port_res, regmap, base[0]); if (ret) return ret; - ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm->pmic_typec_pdphy, + ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm, res->pdphy_res, regmap, base[1]); if (ret) return ret; - mutex_init(&tcpm->lock); platform_set_drvdata(pdev, tcpm); tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector"); @@ -226,13 +92,11 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) goto fwnode_remove; } - ret = qcom_pmic_typec_port_start(tcpm->pmic_typec_port, - tcpm->tcpm_port); + ret = tcpm->port_start(tcpm, tcpm->tcpm_port); if (ret) goto fwnode_remove; - ret = qcom_pmic_typec_pdphy_start(tcpm->pmic_typec_pdphy, - tcpm->tcpm_port); + ret = tcpm->pdphy_start(tcpm, tcpm->tcpm_port); if (ret) goto fwnode_remove; @@ -248,85 +112,13 @@ static void qcom_pmic_typec_remove(struct platform_device *pdev) { struct pmic_typec *tcpm = platform_get_drvdata(pdev); - qcom_pmic_typec_pdphy_stop(tcpm->pmic_typec_pdphy); - qcom_pmic_typec_port_stop(tcpm->pmic_typec_port); + tcpm->pdphy_stop(tcpm); + tcpm->port_stop(tcpm); tcpm_unregister_port(tcpm->tcpm_port); fwnode_remove_software_node(tcpm->tcpc.fwnode); } -static struct pmic_typec_pdphy_resources pm8150b_pdphy_res = { - .irq_params = { - { - .virq = PMIC_PDPHY_SIG_TX_IRQ, - .irq_name = "sig-tx", - }, - { - .virq = PMIC_PDPHY_SIG_RX_IRQ, - .irq_name = "sig-rx", - }, - { - .virq = PMIC_PDPHY_MSG_TX_IRQ, - .irq_name = "msg-tx", - }, - { - .virq = PMIC_PDPHY_MSG_RX_IRQ, - .irq_name = "msg-rx", - }, - { - .virq = PMIC_PDPHY_MSG_TX_FAIL_IRQ, - .irq_name = "msg-tx-failed", - }, - { - .virq = PMIC_PDPHY_MSG_TX_DISCARD_IRQ, - .irq_name = "msg-tx-discarded", - }, - { - .virq = PMIC_PDPHY_MSG_RX_DISCARD_IRQ, - .irq_name = "msg-rx-discarded", - }, - }, - .nr_irqs = 7, -}; - -static struct pmic_typec_port_resources pm8150b_port_res = { - .irq_params = { - { - .irq_name = "vpd-detect", - .virq = PMIC_TYPEC_VPD_IRQ, - }, - - { - .irq_name = "cc-state-change", - .virq = PMIC_TYPEC_CC_STATE_IRQ, - }, - { - .irq_name = "vconn-oc", - .virq = PMIC_TYPEC_VCONN_OC_IRQ, - }, - - { - .irq_name = "vbus-change", - .virq = PMIC_TYPEC_VBUS_IRQ, - }, - - { - .irq_name = "attach-detach", - .virq = PMIC_TYPEC_ATTACH_DETACH_IRQ, - }, - { - .irq_name = "legacy-cable-detect", - .virq = PMIC_TYPEC_LEGACY_CABLE_IRQ, - }, - - { - .irq_name = "try-snk-src-detect", - .virq = PMIC_TYPEC_TRY_SNK_SRC_IRQ, - }, - }, - .nr_irqs = 7, -}; - -static struct pmic_typec_resources pm8150b_typec_res = { +static const struct pmic_typec_resources pm8150b_typec_res = { .pdphy_res = &pm8150b_pdphy_res, .port_res = &pm8150b_port_res, }; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h new file mode 100644 index 000000000000..3c75820c9187 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#ifndef __QCOM_PMIC_TYPEC_H__ +#define __QCOM_PMIC_TYPEC_H__ + +struct pmic_typec { + struct device *dev; + struct tcpm_port *tcpm_port; + struct tcpc_dev tcpc; + struct pmic_typec_pdphy *pmic_typec_pdphy; + struct pmic_typec_port *pmic_typec_port; + + int (*pdphy_start)(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port); + void (*pdphy_stop)(struct pmic_typec *tcpm); + + int (*port_start)(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port); + void (*port_stop)(struct pmic_typec *tcpm); +}; + +#define tcpc_to_tcpm(_tcpc_) container_of(_tcpc_, struct pmic_typec, tcpc) + +#endif diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c index 52c81378e36e..6560f4fc98d5 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c @@ -14,8 +14,74 @@ #include <linux/slab.h> #include <linux/usb/pd.h> #include <linux/usb/tcpm.h> +#include "qcom_pmic_typec.h" #include "qcom_pmic_typec_pdphy.h" +/* PD PHY register offsets and bit fields */ +#define USB_PDPHY_MSG_CONFIG_REG 0x40 +#define MSG_CONFIG_PORT_DATA_ROLE BIT(3) +#define MSG_CONFIG_PORT_POWER_ROLE BIT(2) +#define MSG_CONFIG_SPEC_REV_MASK (BIT(1) | BIT(0)) + +#define USB_PDPHY_EN_CONTROL_REG 0x46 +#define CONTROL_ENABLE BIT(0) + +#define USB_PDPHY_RX_STATUS_REG 0x4A +#define RX_FRAME_TYPE (BIT(0) | BIT(1) | BIT(2)) + +#define USB_PDPHY_FRAME_FILTER_REG 0x4C +#define FRAME_FILTER_EN_HARD_RESET BIT(5) +#define FRAME_FILTER_EN_SOP BIT(0) + +#define USB_PDPHY_TX_SIZE_REG 0x42 +#define TX_SIZE_MASK 0xF + +#define USB_PDPHY_TX_CONTROL_REG 0x44 +#define TX_CONTROL_RETRY_COUNT(n) (((n) & 0x3) << 5) +#define TX_CONTROL_FRAME_TYPE(n) (((n) & 0x7) << 2) +#define TX_CONTROL_FRAME_TYPE_CABLE_RESET (0x1 << 2) +#define TX_CONTROL_SEND_SIGNAL BIT(1) +#define TX_CONTROL_SEND_MSG BIT(0) + +#define USB_PDPHY_RX_SIZE_REG 0x48 + +#define USB_PDPHY_RX_ACKNOWLEDGE_REG 0x4B +#define RX_BUFFER_TOKEN BIT(0) + +#define USB_PDPHY_BIST_MODE_REG 0x4E +#define BIST_MODE_MASK 0xF +#define BIST_ENABLE BIT(7) +#define PD_MSG_BIST 0x3 +#define PD_BIST_TEST_DATA_MODE 0x8 + +#define USB_PDPHY_TX_BUFFER_HDR_REG 0x60 +#define USB_PDPHY_TX_BUFFER_DATA_REG 0x62 + +#define USB_PDPHY_RX_BUFFER_REG 0x80 + +/* VDD regulator */ +#define VDD_PDPHY_VOL_MIN 2800000 /* uV */ +#define VDD_PDPHY_VOL_MAX 3300000 /* uV */ +#define VDD_PDPHY_HPM_LOAD 3000 /* uA */ + +/* Message Spec Rev field */ +#define PD_MSG_HDR_REV(hdr) (((hdr) >> 6) & 3) + +/* timers */ +#define RECEIVER_RESPONSE_TIME 15 /* tReceiverResponse */ +#define HARD_RESET_COMPLETE_TIME 5 /* tHardResetComplete */ + +/* Interrupt numbers */ +#define PMIC_PDPHY_SIG_TX_IRQ 0x0 +#define PMIC_PDPHY_SIG_RX_IRQ 0x1 +#define PMIC_PDPHY_MSG_TX_IRQ 0x2 +#define PMIC_PDPHY_MSG_RX_IRQ 0x3 +#define PMIC_PDPHY_MSG_TX_FAIL_IRQ 0x4 +#define PMIC_PDPHY_MSG_TX_DISCARD_IRQ 0x5 +#define PMIC_PDPHY_MSG_RX_DISCARD_IRQ 0x6 +#define PMIC_PDPHY_FR_SWAP_IRQ 0x7 + + struct pmic_typec_pdphy_irq_data { int virq; int irq; @@ -231,11 +297,13 @@ done: return ret; } -int qcom_pmic_typec_pdphy_pd_transmit(struct pmic_typec_pdphy *pmic_typec_pdphy, - enum tcpm_transmit_type type, - const struct pd_message *msg, - unsigned int negotiated_rev) +static int qcom_pmic_typec_pdphy_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; struct device *dev = pmic_typec_pdphy->dev; int ret; @@ -299,7 +367,7 @@ done: if (!ret) { dev_vdbg(dev, "pd_receive: handing %d bytes to tcpm\n", size); - tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg); + tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg, TCPC_TX_SOP); } } @@ -336,8 +404,10 @@ static irqreturn_t qcom_pmic_typec_pdphy_isr(int irq, void *dev_id) return IRQ_HANDLED; } -int qcom_pmic_typec_pdphy_set_pd_rx(struct pmic_typec_pdphy *pmic_typec_pdphy, bool on) +static int qcom_pmic_typec_pdphy_set_pd_rx(struct tcpc_dev *tcpc, bool on) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; unsigned long flags; int ret; @@ -353,9 +423,12 @@ int qcom_pmic_typec_pdphy_set_pd_rx(struct pmic_typec_pdphy *pmic_typec_pdphy, b return ret; } -int qcom_pmic_typec_pdphy_set_roles(struct pmic_typec_pdphy *pmic_typec_pdphy, - bool data_role_host, bool power_role_src) +static int qcom_pmic_typec_pdphy_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role power_role, + enum typec_data_role data_role) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; struct device *dev = pmic_typec_pdphy->dev; unsigned long flags; int ret; @@ -366,12 +439,13 @@ int qcom_pmic_typec_pdphy_set_roles(struct pmic_typec_pdphy *pmic_typec_pdphy, pmic_typec_pdphy->base + USB_PDPHY_MSG_CONFIG_REG, MSG_CONFIG_PORT_DATA_ROLE | MSG_CONFIG_PORT_POWER_ROLE, - data_role_host << 3 | power_role_src << 2); + (data_role == TYPEC_HOST ? MSG_CONFIG_PORT_DATA_ROLE : 0) | + (power_role == TYPEC_SOURCE ? MSG_CONFIG_PORT_POWER_ROLE : 0)); spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); dev_dbg(dev, "pdphy_set_roles: data_role_host=%d power_role_src=%d\n", - data_role_host, power_role_src); + data_role, power_role); return ret; } @@ -435,9 +509,10 @@ done: return ret; } -int qcom_pmic_typec_pdphy_start(struct pmic_typec_pdphy *pmic_typec_pdphy, - struct tcpm_port *tcpm_port) +static int qcom_pmic_typec_pdphy_start(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port) { + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; int i; int ret; @@ -457,8 +532,9 @@ int qcom_pmic_typec_pdphy_start(struct pmic_typec_pdphy *pmic_typec_pdphy, return 0; } -void qcom_pmic_typec_pdphy_stop(struct pmic_typec_pdphy *pmic_typec_pdphy) +static void qcom_pmic_typec_pdphy_stop(struct pmic_typec *tcpm) { + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; int i; for (i = 0; i < pmic_typec_pdphy->nr_irqs; i++) @@ -469,21 +545,21 @@ void qcom_pmic_typec_pdphy_stop(struct pmic_typec_pdphy *pmic_typec_pdphy) regulator_disable(pmic_typec_pdphy->vdd_pdphy); } -struct pmic_typec_pdphy *qcom_pmic_typec_pdphy_alloc(struct device *dev) -{ - return devm_kzalloc(dev, sizeof(struct pmic_typec_pdphy), GFP_KERNEL); -} - int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, - struct pmic_typec_pdphy *pmic_typec_pdphy, - struct pmic_typec_pdphy_resources *res, + struct pmic_typec *tcpm, + const struct pmic_typec_pdphy_resources *res, struct regmap *regmap, u32 base) { + struct pmic_typec_pdphy *pmic_typec_pdphy; struct device *dev = &pdev->dev; struct pmic_typec_pdphy_irq_data *irq_data; int i, ret, irq; + pmic_typec_pdphy = devm_kzalloc(dev, sizeof(*pmic_typec_pdphy), GFP_KERNEL); + if (!pmic_typec_pdphy) + return -ENOMEM; + if (!res->nr_irqs || res->nr_irqs > PMIC_PDPHY_MAX_IRQS) return -EINVAL; @@ -522,5 +598,48 @@ int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, return ret; } + tcpm->pmic_typec_pdphy = pmic_typec_pdphy; + + tcpm->tcpc.set_pd_rx = qcom_pmic_typec_pdphy_set_pd_rx; + tcpm->tcpc.set_roles = qcom_pmic_typec_pdphy_set_roles; + tcpm->tcpc.pd_transmit = qcom_pmic_typec_pdphy_pd_transmit; + + tcpm->pdphy_start = qcom_pmic_typec_pdphy_start; + tcpm->pdphy_stop = qcom_pmic_typec_pdphy_stop; + return 0; } + +const struct pmic_typec_pdphy_resources pm8150b_pdphy_res = { + .irq_params = { + { + .virq = PMIC_PDPHY_SIG_TX_IRQ, + .irq_name = "sig-tx", + }, + { + .virq = PMIC_PDPHY_SIG_RX_IRQ, + .irq_name = "sig-rx", + }, + { + .virq = PMIC_PDPHY_MSG_TX_IRQ, + .irq_name = "msg-tx", + }, + { + .virq = PMIC_PDPHY_MSG_RX_IRQ, + .irq_name = "msg-rx", + }, + { + .virq = PMIC_PDPHY_MSG_TX_FAIL_IRQ, + .irq_name = "msg-tx-failed", + }, + { + .virq = PMIC_PDPHY_MSG_TX_DISCARD_IRQ, + .irq_name = "msg-tx-discarded", + }, + { + .virq = PMIC_PDPHY_MSG_RX_DISCARD_IRQ, + .irq_name = "msg-rx-discarded", + }, + }, + .nr_irqs = 7, +}; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h index e67954e31b14..5f428e67ccfe 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h @@ -8,74 +8,6 @@ #include <linux/platform_device.h> #include <linux/regmap.h> -#include <linux/usb/tcpm.h> - -#define USB_PDPHY_MAX_DATA_OBJ_LEN 28 -#define USB_PDPHY_MSG_HDR_LEN 2 - -/* PD PHY register offsets and bit fields */ -#define USB_PDPHY_MSG_CONFIG_REG 0x40 -#define MSG_CONFIG_PORT_DATA_ROLE BIT(3) -#define MSG_CONFIG_PORT_POWER_ROLE BIT(2) -#define MSG_CONFIG_SPEC_REV_MASK (BIT(1) | BIT(0)) - -#define USB_PDPHY_EN_CONTROL_REG 0x46 -#define CONTROL_ENABLE BIT(0) - -#define USB_PDPHY_RX_STATUS_REG 0x4A -#define RX_FRAME_TYPE (BIT(0) | BIT(1) | BIT(2)) - -#define USB_PDPHY_FRAME_FILTER_REG 0x4C -#define FRAME_FILTER_EN_HARD_RESET BIT(5) -#define FRAME_FILTER_EN_SOP BIT(0) - -#define USB_PDPHY_TX_SIZE_REG 0x42 -#define TX_SIZE_MASK 0xF - -#define USB_PDPHY_TX_CONTROL_REG 0x44 -#define TX_CONTROL_RETRY_COUNT(n) (((n) & 0x3) << 5) -#define TX_CONTROL_FRAME_TYPE(n) (((n) & 0x7) << 2) -#define TX_CONTROL_FRAME_TYPE_CABLE_RESET (0x1 << 2) -#define TX_CONTROL_SEND_SIGNAL BIT(1) -#define TX_CONTROL_SEND_MSG BIT(0) - -#define USB_PDPHY_RX_SIZE_REG 0x48 - -#define USB_PDPHY_RX_ACKNOWLEDGE_REG 0x4B -#define RX_BUFFER_TOKEN BIT(0) - -#define USB_PDPHY_BIST_MODE_REG 0x4E -#define BIST_MODE_MASK 0xF -#define BIST_ENABLE BIT(7) -#define PD_MSG_BIST 0x3 -#define PD_BIST_TEST_DATA_MODE 0x8 - -#define USB_PDPHY_TX_BUFFER_HDR_REG 0x60 -#define USB_PDPHY_TX_BUFFER_DATA_REG 0x62 - -#define USB_PDPHY_RX_BUFFER_REG 0x80 - -/* VDD regulator */ -#define VDD_PDPHY_VOL_MIN 2800000 /* uV */ -#define VDD_PDPHY_VOL_MAX 3300000 /* uV */ -#define VDD_PDPHY_HPM_LOAD 3000 /* uA */ - -/* Message Spec Rev field */ -#define PD_MSG_HDR_REV(hdr) (((hdr) >> 6) & 3) - -/* timers */ -#define RECEIVER_RESPONSE_TIME 15 /* tReceiverResponse */ -#define HARD_RESET_COMPLETE_TIME 5 /* tHardResetComplete */ - -/* Interrupt numbers */ -#define PMIC_PDPHY_SIG_TX_IRQ 0x0 -#define PMIC_PDPHY_SIG_RX_IRQ 0x1 -#define PMIC_PDPHY_MSG_TX_IRQ 0x2 -#define PMIC_PDPHY_MSG_RX_IRQ 0x3 -#define PMIC_PDPHY_MSG_TX_FAIL_IRQ 0x4 -#define PMIC_PDPHY_MSG_TX_DISCARD_IRQ 0x5 -#define PMIC_PDPHY_MSG_RX_DISCARD_IRQ 0x6 -#define PMIC_PDPHY_FR_SWAP_IRQ 0x7 /* Resources */ #define PMIC_PDPHY_MAX_IRQS 0x08 @@ -87,33 +19,17 @@ struct pmic_typec_pdphy_irq_params { struct pmic_typec_pdphy_resources { unsigned int nr_irqs; - struct pmic_typec_pdphy_irq_params irq_params[PMIC_PDPHY_MAX_IRQS]; + const struct pmic_typec_pdphy_irq_params irq_params[PMIC_PDPHY_MAX_IRQS]; }; /* API */ struct pmic_typec_pdphy; -struct pmic_typec_pdphy *qcom_pmic_typec_pdphy_alloc(struct device *dev); - +extern const struct pmic_typec_pdphy_resources pm8150b_pdphy_res; int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, - struct pmic_typec_pdphy *pmic_typec_pdphy, - struct pmic_typec_pdphy_resources *res, + struct pmic_typec *tcpm, + const struct pmic_typec_pdphy_resources *res, struct regmap *regmap, u32 base); -int qcom_pmic_typec_pdphy_start(struct pmic_typec_pdphy *pmic_typec_pdphy, - struct tcpm_port *tcpm_port); - -void qcom_pmic_typec_pdphy_stop(struct pmic_typec_pdphy *pmic_typec_pdphy); - -int qcom_pmic_typec_pdphy_set_roles(struct pmic_typec_pdphy *pmic_typec_pdphy, - bool power_role_src, bool data_role_host); - -int qcom_pmic_typec_pdphy_set_pd_rx(struct pmic_typec_pdphy *pmic_typec_pdphy, bool on); - -int qcom_pmic_typec_pdphy_pd_transmit(struct pmic_typec_pdphy *pmic_typec_pdphy, - enum tcpm_transmit_type type, - const struct pd_message *msg, - unsigned int negotiated_rev); - #endif /* __QCOM_PMIC_TYPEC_PDPHY_H__ */ diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c index a8f3f4d3a450..a747baa29784 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -16,8 +16,147 @@ #include <linux/usb/tcpm.h> #include <linux/usb/typec_mux.h> #include <linux/workqueue.h> + +#include "qcom_pmic_typec.h" #include "qcom_pmic_typec_port.h" +#define TYPEC_SNK_STATUS_REG 0x06 +#define DETECTED_SNK_TYPE_MASK GENMASK(6, 0) +#define SNK_DAM_MASK GENMASK(6, 4) +#define SNK_DAM_500MA BIT(6) +#define SNK_DAM_1500MA BIT(5) +#define SNK_DAM_3000MA BIT(4) +#define SNK_RP_STD BIT(3) +#define SNK_RP_1P5 BIT(2) +#define SNK_RP_3P0 BIT(1) +#define SNK_RP_SHORT BIT(0) + +#define TYPEC_SRC_STATUS_REG 0x08 +#define DETECTED_SRC_TYPE_MASK GENMASK(4, 0) +#define SRC_HIGH_BATT BIT(5) +#define SRC_DEBUG_ACCESS BIT(4) +#define SRC_RD_OPEN BIT(3) +#define SRC_RD_RA_VCONN BIT(2) +#define SRC_RA_OPEN BIT(1) +#define AUDIO_ACCESS_RA_RA BIT(0) + +#define TYPEC_STATE_MACHINE_STATUS_REG 0x09 +#define TYPEC_ATTACH_DETACH_STATE BIT(5) + +#define TYPEC_SM_STATUS_REG 0x0A +#define TYPEC_SM_VBUS_VSAFE5V BIT(5) +#define TYPEC_SM_VBUS_VSAFE0V BIT(6) +#define TYPEC_SM_USBIN_LT_LV BIT(7) + +#define TYPEC_MISC_STATUS_REG 0x0B +#define TYPEC_WATER_DETECTION_STATUS BIT(7) +#define SNK_SRC_MODE BIT(6) +#define TYPEC_VBUS_DETECT BIT(5) +#define TYPEC_VBUS_ERROR_STATUS BIT(4) +#define TYPEC_DEBOUNCE_DONE BIT(3) +#define CC_ORIENTATION BIT(1) +#define CC_ATTACHED BIT(0) + +#define LEGACY_CABLE_STATUS_REG 0x0D +#define TYPEC_LEGACY_CABLE_STATUS BIT(1) +#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS BIT(0) + +#define TYPEC_U_USB_STATUS_REG 0x0F +#define U_USB_GROUND_NOVBUS BIT(6) +#define U_USB_GROUND BIT(4) +#define U_USB_FMB1 BIT(3) +#define U_USB_FLOAT1 BIT(2) +#define U_USB_FMB2 BIT(1) +#define U_USB_FLOAT2 BIT(0) + +#define TYPEC_MODE_CFG_REG 0x44 +#define TYPEC_TRY_MODE_MASK GENMASK(4, 3) +#define EN_TRY_SNK BIT(4) +#define EN_TRY_SRC BIT(3) +#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) +#define EN_SRC_ONLY BIT(2) +#define EN_SNK_ONLY BIT(1) +#define TYPEC_DISABLE_CMD BIT(0) + +#define TYPEC_VCONN_CONTROL_REG 0x46 +#define VCONN_EN_ORIENTATION BIT(2) +#define VCONN_EN_VALUE BIT(1) +#define VCONN_EN_SRC BIT(0) + +#define TYPEC_CCOUT_CONTROL_REG 0x48 +#define TYPEC_CCOUT_BUFFER_EN BIT(2) +#define TYPEC_CCOUT_VALUE BIT(1) +#define TYPEC_CCOUT_SRC BIT(0) + +#define DEBUG_ACCESS_SRC_CFG_REG 0x4C +#define EN_UNORIENTED_DEBUG_ACCESS_SRC BIT(0) + +#define TYPE_C_CRUDE_SENSOR_CFG_REG 0x4e +#define EN_SRC_CRUDE_SENSOR BIT(1) +#define EN_SNK_CRUDE_SENSOR BIT(0) + +#define TYPEC_EXIT_STATE_CFG_REG 0x50 +#define BYPASS_VSAFE0V_DURING_ROLE_SWAP BIT(3) +#define SEL_SRC_UPPER_REF BIT(2) +#define USE_TPD_FOR_EXITING_ATTACHSRC BIT(1) +#define EXIT_SNK_BASED_ON_CC BIT(0) + +#define TYPEC_CURRSRC_CFG_REG 0x52 +#define TYPEC_SRC_RP_SEL_330UA BIT(1) +#define TYPEC_SRC_RP_SEL_180UA BIT(0) +#define TYPEC_SRC_RP_SEL_80UA 0 +#define TYPEC_SRC_RP_SEL_MASK GENMASK(1, 0) + +#define TYPEC_INTERRUPT_EN_CFG_1_REG 0x5E +#define TYPEC_LEGACY_CABLE_INT_EN BIT(7) +#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN BIT(6) +#define TYPEC_TRYSOURCE_DETECT_INT_EN BIT(5) +#define TYPEC_TRYSINK_DETECT_INT_EN BIT(4) +#define TYPEC_CCOUT_DETACH_INT_EN BIT(3) +#define TYPEC_CCOUT_ATTACH_INT_EN BIT(2) +#define TYPEC_VBUS_DEASSERT_INT_EN BIT(1) +#define TYPEC_VBUS_ASSERT_INT_EN BIT(0) + +#define TYPEC_INTERRUPT_EN_CFG_2_REG 0x60 +#define TYPEC_SRC_BATT_HPWR_INT_EN BIT(6) +#define MICRO_USB_STATE_CHANGE_INT_EN BIT(5) +#define TYPEC_STATE_MACHINE_CHANGE_INT_EN BIT(4) +#define TYPEC_DEBUG_ACCESS_DETECT_INT_EN BIT(3) +#define TYPEC_WATER_DETECTION_INT_EN BIT(2) +#define TYPEC_VBUS_ERROR_INT_EN BIT(1) +#define TYPEC_DEBOUNCE_DONE_INT_EN BIT(0) + +#define TYPEC_DEBOUNCE_OPTION_REG 0x62 +#define REDUCE_TCCDEBOUNCE_TO_2MS BIT(2) + +#define TYPE_C_SBU_CFG_REG 0x6A +#define SEL_SBU1_ISRC_VAL 0x04 +#define SEL_SBU2_ISRC_VAL 0x01 + +#define TYPEC_U_USB_CFG_REG 0x70 +#define EN_MICRO_USB_FACTORY_MODE BIT(1) +#define EN_MICRO_USB_MODE BIT(0) + +#define TYPEC_PMI632_U_USB_WATER_PROTECTION_CFG_REG 0x72 + +#define TYPEC_U_USB_WATER_PROTECTION_CFG_REG 0x73 +#define EN_MICRO_USB_WATER_PROTECTION BIT(4) +#define MICRO_USB_DETECTION_ON_TIME_CFG_MASK GENMASK(3, 2) +#define MICRO_USB_DETECTION_PERIOD_CFG_MASK GENMASK(1, 0) + +#define TYPEC_PMI632_MICRO_USB_MODE_REG 0x73 +#define MICRO_USB_MODE_ONLY BIT(0) + +/* Interrupt numbers */ +#define PMIC_TYPEC_OR_RID_IRQ 0x0 +#define PMIC_TYPEC_VPD_IRQ 0x1 +#define PMIC_TYPEC_CC_STATE_IRQ 0x2 +#define PMIC_TYPEC_VCONN_OC_IRQ 0x3 +#define PMIC_TYPEC_VBUS_IRQ 0x4 +#define PMIC_TYPEC_ATTACH_DETACH_IRQ 0x5 +#define PMIC_TYPEC_LEGACY_CABLE_IRQ 0x6 +#define PMIC_TYPEC_TRY_SNK_SRC_IRQ 0x7 + struct pmic_typec_port_irq_data { int virq; int irq; @@ -33,6 +172,8 @@ struct pmic_typec_port { struct pmic_typec_port_irq_data *irq_data; struct regulator *vdd_vbus; + bool vbus_enabled; + struct mutex vbus_lock; /* VBUS state serialization */ int cc; bool debouncing_cc; @@ -131,7 +272,7 @@ done: return IRQ_HANDLED; } -int qcom_pmic_typec_port_get_vbus(struct pmic_typec_port *pmic_typec_port) +static int qcom_pmic_typec_port_vbus_detect(struct pmic_typec_port *pmic_typec_port) { struct device *dev = pmic_typec_port->dev; unsigned int misc; @@ -148,7 +289,7 @@ int qcom_pmic_typec_port_get_vbus(struct pmic_typec_port *pmic_typec_port) return !!(misc & TYPEC_VBUS_DETECT); } -int qcom_pmic_typec_port_set_vbus(struct pmic_typec_port *pmic_typec_port, bool on) +static int qcom_pmic_typec_port_vbus_toggle(struct pmic_typec_port *pmic_typec_port, bool on) { u32 sm_stat; u32 val; @@ -179,10 +320,49 @@ int qcom_pmic_typec_port_set_vbus(struct pmic_typec_port *pmic_typec_port, bool return 0; } -int qcom_pmic_typec_port_get_cc(struct pmic_typec_port *pmic_typec_port, - enum typec_cc_status *cc1, - enum typec_cc_status *cc2) +static int qcom_pmic_typec_port_get_vbus(struct tcpc_dev *tcpc) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + int ret; + + mutex_lock(&pmic_typec_port->vbus_lock); + ret = pmic_typec_port->vbus_enabled || qcom_pmic_typec_port_vbus_detect(pmic_typec_port); + mutex_unlock(&pmic_typec_port->vbus_lock); + + return ret; +} + +static int qcom_pmic_typec_port_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + int ret = 0; + + mutex_lock(&pmic_typec_port->vbus_lock); + if (pmic_typec_port->vbus_enabled == on) + goto done; + + ret = qcom_pmic_typec_port_vbus_toggle(pmic_typec_port, on); + if (ret) + goto done; + + pmic_typec_port->vbus_enabled = on; + tcpm_vbus_change(tcpm->tcpm_port); + +done: + dev_dbg(tcpm->dev, "set_vbus set: %d result %d\n", on, ret); + mutex_unlock(&pmic_typec_port->vbus_lock); + + return ret; +} + +static int qcom_pmic_typec_port_get_cc(struct tcpc_dev *tcpc, + enum typec_cc_status *cc1, + enum typec_cc_status *cc2) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; struct device *dev = pmic_typec_port->dev; unsigned int misc, val; bool attached; @@ -275,9 +455,11 @@ static void qcom_pmic_set_cc_debounce(struct pmic_typec_port *pmic_typec_port) msecs_to_jiffies(2)); } -int qcom_pmic_typec_port_set_cc(struct pmic_typec_port *pmic_typec_port, - enum typec_cc_status cc) +static int qcom_pmic_typec_port_set_cc(struct tcpc_dev *tcpc, + enum typec_cc_status cc) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; struct device *dev = pmic_typec_port->dev; unsigned int mode, currsrc; unsigned int misc; @@ -341,8 +523,17 @@ done: return ret; } -int qcom_pmic_typec_port_set_vconn(struct pmic_typec_port *pmic_typec_port, bool on) +static int qcom_pmic_typec_port_set_polarity(struct tcpc_dev *tcpc, + enum typec_cc_polarity pol) +{ + /* Polarity is set separately by phy-qcom-qmp.c */ + return 0; +} + +static int qcom_pmic_typec_port_set_vconn(struct tcpc_dev *tcpc, bool on) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; struct device *dev = pmic_typec_port->dev; unsigned int orientation, misc, mask, value; unsigned long flags; @@ -377,10 +568,12 @@ done: return ret; } -int qcom_pmic_typec_port_start_toggling(struct pmic_typec_port *pmic_typec_port, - enum typec_port_type port_type, - enum typec_cc_status cc) +static int qcom_pmic_typec_port_start_toggling(struct tcpc_dev *tcpc, + enum typec_port_type port_type, + enum typec_cc_status cc) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; struct device *dev = pmic_typec_port->dev; unsigned int misc; u8 mode = 0; @@ -441,9 +634,10 @@ done: (TYPEC_STATE_MACHINE_CHANGE_INT_EN | TYPEC_VBUS_ERROR_INT_EN | \ TYPEC_DEBOUNCE_DONE_INT_EN) -int qcom_pmic_typec_port_start(struct pmic_typec_port *pmic_typec_port, - struct tcpm_port *tcpm_port) +static int qcom_pmic_typec_port_start(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port) { + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; int i; int mask; int ret; @@ -491,29 +685,30 @@ done: return ret; } -void qcom_pmic_typec_port_stop(struct pmic_typec_port *pmic_typec_port) +static void qcom_pmic_typec_port_stop(struct pmic_typec *tcpm) { + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; int i; for (i = 0; i < pmic_typec_port->nr_irqs; i++) disable_irq(pmic_typec_port->irq_data[i].irq); } -struct pmic_typec_port *qcom_pmic_typec_port_alloc(struct device *dev) -{ - return devm_kzalloc(dev, sizeof(struct pmic_typec_port), GFP_KERNEL); -} - int qcom_pmic_typec_port_probe(struct platform_device *pdev, - struct pmic_typec_port *pmic_typec_port, - struct pmic_typec_port_resources *res, + struct pmic_typec *tcpm, + const struct pmic_typec_port_resources *res, struct regmap *regmap, u32 base) { struct device *dev = &pdev->dev; struct pmic_typec_port_irq_data *irq_data; + struct pmic_typec_port *pmic_typec_port; int i, ret, irq; + pmic_typec_port = devm_kzalloc(dev, sizeof(*pmic_typec_port), GFP_KERNEL); + if (!pmic_typec_port) + return -ENOMEM; + if (!res->nr_irqs || res->nr_irqs > PMIC_TYPEC_MAX_IRQS) return -EINVAL; @@ -522,6 +717,8 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev, if (!irq_data) return -ENOMEM; + mutex_init(&pmic_typec_port->vbus_lock); + pmic_typec_port->vdd_vbus = devm_regulator_get(dev, "vdd-vbus"); if (IS_ERR(pmic_typec_port->vdd_vbus)) return PTR_ERR(pmic_typec_port->vdd_vbus); @@ -556,5 +753,56 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev, return ret; } + tcpm->pmic_typec_port = pmic_typec_port; + + tcpm->tcpc.get_vbus = qcom_pmic_typec_port_get_vbus; + tcpm->tcpc.set_vbus = qcom_pmic_typec_port_set_vbus; + tcpm->tcpc.set_cc = qcom_pmic_typec_port_set_cc; + tcpm->tcpc.get_cc = qcom_pmic_typec_port_get_cc; + tcpm->tcpc.set_polarity = qcom_pmic_typec_port_set_polarity; + tcpm->tcpc.set_vconn = qcom_pmic_typec_port_set_vconn; + tcpm->tcpc.start_toggling = qcom_pmic_typec_port_start_toggling; + + tcpm->port_start = qcom_pmic_typec_port_start; + tcpm->port_stop = qcom_pmic_typec_port_stop; + return 0; } + +const struct pmic_typec_port_resources pm8150b_port_res = { + .irq_params = { + { + .irq_name = "vpd-detect", + .virq = PMIC_TYPEC_VPD_IRQ, + }, + + { + .irq_name = "cc-state-change", + .virq = PMIC_TYPEC_CC_STATE_IRQ, + }, + { + .irq_name = "vconn-oc", + .virq = PMIC_TYPEC_VCONN_OC_IRQ, + }, + + { + .irq_name = "vbus-change", + .virq = PMIC_TYPEC_VBUS_IRQ, + }, + + { + .irq_name = "attach-detach", + .virq = PMIC_TYPEC_ATTACH_DETACH_IRQ, + }, + { + .irq_name = "legacy-cable-detect", + .virq = PMIC_TYPEC_LEGACY_CABLE_IRQ, + }, + + { + .irq_name = "try-snk-src-detect", + .virq = PMIC_TYPEC_TRY_SNK_SRC_IRQ, + }, + }, + .nr_irqs = 7, +}; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h index d4d358c680b6..2ca83a46cf3b 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h @@ -3,149 +3,12 @@ * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. * Copyright (c) 2023, Linaro Ltd. All rights reserved. */ -#ifndef __QCOM_PMIC_TYPEC_H__ -#define __QCOM_PMIC_TYPEC_H__ +#ifndef __QCOM_PMIC_TYPEC_PORT_H__ +#define __QCOM_PMIC_TYPEC_PORT_H__ #include <linux/platform_device.h> #include <linux/usb/tcpm.h> -#define TYPEC_SNK_STATUS_REG 0x06 -#define DETECTED_SNK_TYPE_MASK GENMASK(6, 0) -#define SNK_DAM_MASK GENMASK(6, 4) -#define SNK_DAM_500MA BIT(6) -#define SNK_DAM_1500MA BIT(5) -#define SNK_DAM_3000MA BIT(4) -#define SNK_RP_STD BIT(3) -#define SNK_RP_1P5 BIT(2) -#define SNK_RP_3P0 BIT(1) -#define SNK_RP_SHORT BIT(0) - -#define TYPEC_SRC_STATUS_REG 0x08 -#define DETECTED_SRC_TYPE_MASK GENMASK(4, 0) -#define SRC_HIGH_BATT BIT(5) -#define SRC_DEBUG_ACCESS BIT(4) -#define SRC_RD_OPEN BIT(3) -#define SRC_RD_RA_VCONN BIT(2) -#define SRC_RA_OPEN BIT(1) -#define AUDIO_ACCESS_RA_RA BIT(0) - -#define TYPEC_STATE_MACHINE_STATUS_REG 0x09 -#define TYPEC_ATTACH_DETACH_STATE BIT(5) - -#define TYPEC_SM_STATUS_REG 0x0A -#define TYPEC_SM_VBUS_VSAFE5V BIT(5) -#define TYPEC_SM_VBUS_VSAFE0V BIT(6) -#define TYPEC_SM_USBIN_LT_LV BIT(7) - -#define TYPEC_MISC_STATUS_REG 0x0B -#define TYPEC_WATER_DETECTION_STATUS BIT(7) -#define SNK_SRC_MODE BIT(6) -#define TYPEC_VBUS_DETECT BIT(5) -#define TYPEC_VBUS_ERROR_STATUS BIT(4) -#define TYPEC_DEBOUNCE_DONE BIT(3) -#define CC_ORIENTATION BIT(1) -#define CC_ATTACHED BIT(0) - -#define LEGACY_CABLE_STATUS_REG 0x0D -#define TYPEC_LEGACY_CABLE_STATUS BIT(1) -#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS BIT(0) - -#define TYPEC_U_USB_STATUS_REG 0x0F -#define U_USB_GROUND_NOVBUS BIT(6) -#define U_USB_GROUND BIT(4) -#define U_USB_FMB1 BIT(3) -#define U_USB_FLOAT1 BIT(2) -#define U_USB_FMB2 BIT(1) -#define U_USB_FLOAT2 BIT(0) - -#define TYPEC_MODE_CFG_REG 0x44 -#define TYPEC_TRY_MODE_MASK GENMASK(4, 3) -#define EN_TRY_SNK BIT(4) -#define EN_TRY_SRC BIT(3) -#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) -#define EN_SRC_ONLY BIT(2) -#define EN_SNK_ONLY BIT(1) -#define TYPEC_DISABLE_CMD BIT(0) - -#define TYPEC_VCONN_CONTROL_REG 0x46 -#define VCONN_EN_ORIENTATION BIT(2) -#define VCONN_EN_VALUE BIT(1) -#define VCONN_EN_SRC BIT(0) - -#define TYPEC_CCOUT_CONTROL_REG 0x48 -#define TYPEC_CCOUT_BUFFER_EN BIT(2) -#define TYPEC_CCOUT_VALUE BIT(1) -#define TYPEC_CCOUT_SRC BIT(0) - -#define DEBUG_ACCESS_SRC_CFG_REG 0x4C -#define EN_UNORIENTED_DEBUG_ACCESS_SRC BIT(0) - -#define TYPE_C_CRUDE_SENSOR_CFG_REG 0x4e -#define EN_SRC_CRUDE_SENSOR BIT(1) -#define EN_SNK_CRUDE_SENSOR BIT(0) - -#define TYPEC_EXIT_STATE_CFG_REG 0x50 -#define BYPASS_VSAFE0V_DURING_ROLE_SWAP BIT(3) -#define SEL_SRC_UPPER_REF BIT(2) -#define USE_TPD_FOR_EXITING_ATTACHSRC BIT(1) -#define EXIT_SNK_BASED_ON_CC BIT(0) - -#define TYPEC_CURRSRC_CFG_REG 0x52 -#define TYPEC_SRC_RP_SEL_330UA BIT(1) -#define TYPEC_SRC_RP_SEL_180UA BIT(0) -#define TYPEC_SRC_RP_SEL_80UA 0 -#define TYPEC_SRC_RP_SEL_MASK GENMASK(1, 0) - -#define TYPEC_INTERRUPT_EN_CFG_1_REG 0x5E -#define TYPEC_LEGACY_CABLE_INT_EN BIT(7) -#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN BIT(6) -#define TYPEC_TRYSOURCE_DETECT_INT_EN BIT(5) -#define TYPEC_TRYSINK_DETECT_INT_EN BIT(4) -#define TYPEC_CCOUT_DETACH_INT_EN BIT(3) -#define TYPEC_CCOUT_ATTACH_INT_EN BIT(2) -#define TYPEC_VBUS_DEASSERT_INT_EN BIT(1) -#define TYPEC_VBUS_ASSERT_INT_EN BIT(0) - -#define TYPEC_INTERRUPT_EN_CFG_2_REG 0x60 -#define TYPEC_SRC_BATT_HPWR_INT_EN BIT(6) -#define MICRO_USB_STATE_CHANGE_INT_EN BIT(5) -#define TYPEC_STATE_MACHINE_CHANGE_INT_EN BIT(4) -#define TYPEC_DEBUG_ACCESS_DETECT_INT_EN BIT(3) -#define TYPEC_WATER_DETECTION_INT_EN BIT(2) -#define TYPEC_VBUS_ERROR_INT_EN BIT(1) -#define TYPEC_DEBOUNCE_DONE_INT_EN BIT(0) - -#define TYPEC_DEBOUNCE_OPTION_REG 0x62 -#define REDUCE_TCCDEBOUNCE_TO_2MS BIT(2) - -#define TYPE_C_SBU_CFG_REG 0x6A -#define SEL_SBU1_ISRC_VAL 0x04 -#define SEL_SBU2_ISRC_VAL 0x01 - -#define TYPEC_U_USB_CFG_REG 0x70 -#define EN_MICRO_USB_FACTORY_MODE BIT(1) -#define EN_MICRO_USB_MODE BIT(0) - -#define TYPEC_PMI632_U_USB_WATER_PROTECTION_CFG_REG 0x72 - -#define TYPEC_U_USB_WATER_PROTECTION_CFG_REG 0x73 -#define EN_MICRO_USB_WATER_PROTECTION BIT(4) -#define MICRO_USB_DETECTION_ON_TIME_CFG_MASK GENMASK(3, 2) -#define MICRO_USB_DETECTION_PERIOD_CFG_MASK GENMASK(1, 0) - -#define TYPEC_PMI632_MICRO_USB_MODE_REG 0x73 -#define MICRO_USB_MODE_ONLY BIT(0) - -/* Interrupt numbers */ -#define PMIC_TYPEC_OR_RID_IRQ 0x0 -#define PMIC_TYPEC_VPD_IRQ 0x1 -#define PMIC_TYPEC_CC_STATE_IRQ 0x2 -#define PMIC_TYPEC_VCONN_OC_IRQ 0x3 -#define PMIC_TYPEC_VBUS_IRQ 0x4 -#define PMIC_TYPEC_ATTACH_DETACH_IRQ 0x5 -#define PMIC_TYPEC_LEGACY_CABLE_IRQ 0x6 -#define PMIC_TYPEC_TRY_SNK_SRC_IRQ 0x7 - /* Resources */ #define PMIC_TYPEC_MAX_IRQS 0x08 @@ -156,40 +19,17 @@ struct pmic_typec_port_irq_params { struct pmic_typec_port_resources { unsigned int nr_irqs; - struct pmic_typec_port_irq_params irq_params[PMIC_TYPEC_MAX_IRQS]; + const struct pmic_typec_port_irq_params irq_params[PMIC_TYPEC_MAX_IRQS]; }; /* API */ -struct pmic_typec; -struct pmic_typec_port *qcom_pmic_typec_port_alloc(struct device *dev); +extern const struct pmic_typec_port_resources pm8150b_port_res; int qcom_pmic_typec_port_probe(struct platform_device *pdev, - struct pmic_typec_port *pmic_typec_port, - struct pmic_typec_port_resources *res, + struct pmic_typec *tcpm, + const struct pmic_typec_port_resources *res, struct regmap *regmap, u32 base); -int qcom_pmic_typec_port_start(struct pmic_typec_port *pmic_typec_port, - struct tcpm_port *tcpm_port); - -void qcom_pmic_typec_port_stop(struct pmic_typec_port *pmic_typec_port); - -int qcom_pmic_typec_port_get_cc(struct pmic_typec_port *pmic_typec_port, - enum typec_cc_status *cc1, - enum typec_cc_status *cc2); - -int qcom_pmic_typec_port_set_cc(struct pmic_typec_port *pmic_typec_port, - enum typec_cc_status cc); - -int qcom_pmic_typec_port_get_vbus(struct pmic_typec_port *pmic_typec_port); - -int qcom_pmic_typec_port_set_vconn(struct pmic_typec_port *pmic_typec_port, bool on); - -int qcom_pmic_typec_port_start_toggling(struct pmic_typec_port *pmic_typec_port, - enum typec_port_type port_type, - enum typec_cc_status cc); - -int qcom_pmic_typec_port_set_vbus(struct pmic_typec_port *pmic_typec_port, bool on); - #endif /* __QCOM_PMIC_TYPE_C_PORT_H__ */ diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 0ee3e6e29bb1..40c7b6224c74 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -445,8 +445,11 @@ static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) unsigned int reg = 0; int ret; - if (enable) + if (enable) { reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET; + if (tcpci->data->cable_comm_capable) + reg |= TCPC_RX_DETECT_SOP1; + } ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg); if (ret < 0) return ret; @@ -584,6 +587,23 @@ static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type return 0; } +static bool tcpci_cable_comm_capable(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + return tcpci->data->cable_comm_capable; +} + +static bool tcpci_attempt_vconn_swap_discovery(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + if (tcpci->data->attempt_vconn_swap_discovery) + return tcpci->data->attempt_vconn_swap_discovery(tcpci, tcpci->data); + + return false; +} + static int tcpci_init(struct tcpc_dev *tcpc) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); @@ -712,7 +732,7 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) /* Read complete, clear RX status alert bit */ tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS); - tcpm_pd_receive(tcpci->port, &msg); + tcpm_pd_receive(tcpci->port, &msg, TCPC_TX_SOP); } if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) { @@ -793,6 +813,8 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.enable_frs = tcpci_enable_frs; tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus; tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable; + tcpci->tcpc.cable_comm_capable = tcpci_cable_comm_capable; + tcpci->tcpc.attempt_vconn_swap_discovery = tcpci_attempt_vconn_swap_discovery; if (tcpci->data->check_contaminant) tcpci->tcpc.check_contaminant = tcpci_check_contaminant; diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h index 2c1c4d161b0d..78ff3b73ee7e 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.h +++ b/drivers/usb/typec/tcpm/tcpci_maxim.h @@ -62,6 +62,7 @@ struct max_tcpci_chip { struct i2c_client *client; struct tcpm_port *port; enum contamiant_state contaminant_state; + bool veto_vconn_swap; }; static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index 7fb966fd639b..eec3bcec119c 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -128,6 +128,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN]; int ret, payload_index; u8 *rx_buf_ptr; + enum tcpm_transmit_type rx_type; /* * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers @@ -143,10 +144,23 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) 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) { + switch (frame_type) { + case TCPC_RX_BUF_FRAME_TYPE_SOP1: + rx_type = TCPC_TX_SOP_PRIME; + break; + case TCPC_RX_BUF_FRAME_TYPE_SOP: + rx_type = TCPC_TX_SOP; + break; + default: + rx_type = TCPC_TX_SOP; + break; + } + + if (count == 0 || (frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP && + frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP1)) { max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS); dev_err(chip->dev, "%s\n", count == 0 ? "error: count is 0" : - "error frame_type is not SOP"); + "error frame_type is not SOP/SOP'"); return; } @@ -183,7 +197,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) if (ret < 0) return; - tcpm_pd_receive(chip->port, &msg); + tcpm_pd_receive(chip->port, &msg, rx_type); } static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink) @@ -309,8 +323,10 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) if (ret < 0) return ret; - if (reg_status & TCPC_FAULT_STATUS_VCONN_OC) + if (reg_status & TCPC_FAULT_STATUS_VCONN_OC) { + chip->veto_vconn_swap = true; tcpm_port_error_recovery(chip->port); + } } if (status & TCPC_ALERT_EXTND) { @@ -444,6 +460,18 @@ static void max_tcpci_check_contaminant(struct tcpci *tcpci, struct tcpci_data * tcpm_port_clean(chip->port); } +static bool max_tcpci_attempt_vconn_swap_discovery(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + + if (chip->veto_vconn_swap) { + chip->veto_vconn_swap = false; + return false; + } + + return true; +} + static int max_tcpci_probe(struct i2c_client *client) { int ret; @@ -478,6 +506,8 @@ static int max_tcpci_probe(struct i2c_client *client) chip->data.vbus_vsafe0v = true; chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable; chip->data.check_contaminant = max_tcpci_check_contaminant; + chip->data.cable_comm_capable = true; + chip->data.attempt_vconn_swap_discovery = max_tcpci_attempt_vconn_swap_discovery; max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index f7d7daa60c8d..d975fc525eac 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -108,6 +108,7 @@ S(VCONN_SWAP_WAIT_FOR_VCONN), \ S(VCONN_SWAP_TURN_ON_VCONN), \ S(VCONN_SWAP_TURN_OFF_VCONN), \ + S(VCONN_SWAP_SEND_SOFT_RESET), \ \ S(FR_SWAP_SEND), \ S(FR_SWAP_SEND_TIMEOUT), \ @@ -145,7 +146,9 @@ S(PORT_RESET_WAIT_OFF), \ \ S(AMS_START), \ - S(CHUNK_NOT_SUPP) + S(CHUNK_NOT_SUPP), \ + \ + S(SRC_VDM_IDENTITY_REQUEST) #define FOREACH_AMS(S) \ S(NONE_AMS), \ @@ -327,6 +330,12 @@ struct tcpm_port { struct typec_partner_desc partner_desc; struct typec_partner *partner; + struct usb_pd_identity cable_ident; + struct typec_cable_desc cable_desc; + struct typec_cable *cable; + struct typec_plug_desc plug_prime_desc; + struct typec_plug *plug_prime; + enum typec_cc_status cc_req; enum typec_cc_status src_rp; /* work only if pd_supported == false */ @@ -468,7 +477,9 @@ struct tcpm_port { /* Alternate mode data */ struct pd_mode_data mode_data; + struct pd_mode_data mode_data_prime; struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX]; + struct typec_altmode *plug_prime_altmode[ALTMODE_DISCOVERY_MAX]; struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX]; /* Deadline in jiffies to exit src_try_wait state */ @@ -505,6 +516,41 @@ struct tcpm_port { * transitions. */ bool potential_contaminant; + + /* SOP* Related Fields */ + /* + * Flag to determine if SOP' Discover Identity is available. The flag + * is set if Discover Identity on SOP' does not immediately follow + * Discover Identity on SOP. + */ + bool send_discover_prime; + /* + * tx_sop_type determines which SOP* a message is being sent on. + * For messages that are queued and not sent immediately such as in + * tcpm_queue_message or messages that send after state changes, + * the tx_sop_type is set accordingly. + */ + enum tcpm_transmit_type tx_sop_type; + /* + * Prior to discovering the port partner's Specification Revision, the + * Vconn source and cable plug will use the lower of their two revisions. + * + * When the port partner's Specification Revision is discovered, the following + * rules are put in place. + * 1. If the cable revision (1) is lower than the revision negotiated + * between the port and partner (2), the port and partner will communicate + * on revision (2), but the port and cable will communicate on revision (1). + * 2. If the cable revision (1) is higher than the revision negotiated + * between the port and partner (2), the port and partner will communicate + * on revision (2), and the port and cable will communicate on revision (2) + * as well. + */ + unsigned int negotiated_rev_prime; + /* + * Each SOP* type must maintain their own tx and rx message IDs + */ + unsigned int message_id_prime; + unsigned int rx_msgid_prime; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -518,6 +564,7 @@ struct pd_rx_event { struct kthread_work work; struct tcpm_port *port; struct pd_message msg; + enum tcpm_transmit_type rx_sop_type; }; static const char * const pd_rev[] = { @@ -893,19 +940,30 @@ static void tcpm_ams_finish(struct tcpm_port *port) } static int tcpm_pd_transmit(struct tcpm_port *port, - enum tcpm_transmit_type type, + enum tcpm_transmit_type tx_sop_type, const struct pd_message *msg) { unsigned long timeout; int ret; + unsigned int negotiated_rev; + + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + negotiated_rev = port->negotiated_rev_prime; + break; + case TCPC_TX_SOP: + default: + negotiated_rev = port->negotiated_rev; + break; + } if (msg) tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header)); else - tcpm_log(port, "PD TX, type: %#x", type); + tcpm_log(port, "PD TX, type: %#x", tx_sop_type); reinit_completion(&port->tx_complete); - ret = port->tcpc->pd_transmit(port->tcpc, type, msg, port->negotiated_rev); + ret = port->tcpc->pd_transmit(port->tcpc, tx_sop_type, msg, negotiated_rev); if (ret < 0) return ret; @@ -918,7 +976,17 @@ static int tcpm_pd_transmit(struct tcpm_port *port, switch (port->tx_status) { case TCPC_TX_SUCCESS: - port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK; + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + port->message_id_prime = (port->message_id_prime + 1) & + PD_HEADER_ID_MASK; + break; + case TCPC_TX_SOP: + default: + port->message_id = (port->message_id + 1) & + PD_HEADER_ID_MASK; + break; + } /* * USB PD rev 2.0, 8.3.2.2.1: * USB PD rev 3.0, 8.3.2.1.3: @@ -1456,7 +1524,7 @@ static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams) * VDM/VDO handling functions */ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, - const u32 *data, int cnt) + const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) { u32 vdo_hdr = port->vdo_data[0]; @@ -1464,7 +1532,10 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, /* If is sending discover_identity, handle received message first */ if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) { - port->send_discover = true; + if (tx_sop_type == TCPC_TX_SOP_PRIME) + port->send_discover_prime = true; + else + port->send_discover = true; mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); } else { /* Make sure we are not still processing a previous VDM packet */ @@ -1479,14 +1550,16 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, port->vdm_state = VDM_STATE_READY; port->vdm_sm_running = true; + port->tx_sop_type = tx_sop_type; + mod_vdm_delayed_work(port, 0); } static void tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header, - const u32 *data, int cnt) + const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) { mutex_lock(&port->lock); - tcpm_queue_vdm(port, header, data, cnt); + tcpm_queue_vdm(port, header, data, cnt, TCPC_TX_SOP); mutex_unlock(&port->lock); } @@ -1508,9 +1581,68 @@ static void svdm_consume_identity(struct tcpm_port *port, const u32 *p, int cnt) PD_PRODUCT_PID(product), product & 0xffff); } -static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt) +static void svdm_consume_identity_sop_prime(struct tcpm_port *port, const u32 *p, int cnt) { - struct pd_mode_data *pmdata = &port->mode_data; + u32 idh = p[VDO_INDEX_IDH]; + u32 product = p[VDO_INDEX_PRODUCT]; + int svdm_version; + + /* + * Attempt to consume identity only if cable currently is not set + */ + if (!IS_ERR_OR_NULL(port->cable)) + goto register_plug; + + /* Reset cable identity */ + memset(&port->cable_ident, 0, sizeof(port->cable_ident)); + + /* Fill out id header, cert, product, cable VDO 1 */ + port->cable_ident.id_header = idh; + port->cable_ident.cert_stat = p[VDO_INDEX_CSTAT]; + port->cable_ident.product = product; + port->cable_ident.vdo[0] = p[VDO_INDEX_CABLE_1]; + + /* Fill out cable desc, infer svdm_version from pd revision */ + port->cable_desc.type = (enum typec_plug_type) (VDO_TYPEC_CABLE_TYPE(p[VDO_INDEX_CABLE_1]) + + USB_PLUG_TYPE_A); + port->cable_desc.active = PD_IDH_PTYPE(idh) == IDH_PTYPE_ACABLE ? 1 : 0; + /* Log PD Revision and additional cable VDO from negotiated revision */ + switch (port->negotiated_rev_prime) { + case PD_REV30: + port->cable_desc.pd_revision = 0x0300; + if (port->cable_desc.active) + port->cable_ident.vdo[1] = p[VDO_INDEX_CABLE_2]; + break; + case PD_REV20: + port->cable_desc.pd_revision = 0x0200; + break; + default: + port->cable_desc.pd_revision = 0x0200; + break; + } + port->cable_desc.identity = &port->cable_ident; + /* Register Cable, set identity and svdm_version */ + port->cable = typec_register_cable(port->typec_port, &port->cable_desc); + if (IS_ERR_OR_NULL(port->cable)) + return; + typec_cable_set_identity(port->cable); + /* Get SVDM version */ + svdm_version = PD_VDO_SVDM_VER(p[VDO_INDEX_HDR]); + typec_cable_set_svdm_version(port->cable, svdm_version); + +register_plug: + if (IS_ERR_OR_NULL(port->plug_prime)) { + port->plug_prime_desc.index = TYPEC_PLUG_SOP_P; + port->plug_prime = typec_register_plug(port->cable, + &port->plug_prime_desc); + } +} + +static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt, + enum tcpm_transmit_type rx_sop_type) +{ + struct pd_mode_data *pmdata = rx_sop_type == TCPC_TX_SOP_PRIME ? + &port->mode_data_prime : &port->mode_data; int i; for (i = 1; i < cnt; i++) { @@ -1556,14 +1688,29 @@ abort: return false; } -static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt) +static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt, + enum tcpm_transmit_type rx_sop_type) { struct pd_mode_data *pmdata = &port->mode_data; struct typec_altmode_desc *paltmode; int i; - if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) { - /* Already logged in svdm_consume_svids() */ + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + pmdata = &port->mode_data_prime; + if (pmdata->altmodes >= ARRAY_SIZE(port->plug_prime_altmode)) { + /* Already logged in svdm_consume_svids() */ + return; + } + break; + case TCPC_TX_SOP: + pmdata = &port->mode_data; + if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) { + /* Already logged in svdm_consume_svids() */ + return; + } + break; + default: return; } @@ -1601,20 +1748,129 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) } } +static void tcpm_register_plug_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data_prime; + struct typec_altmode *altmode; + int i; + + typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes); + + for (i = 0; i < modep->altmodes; i++) { + altmode = typec_plug_register_altmode(port->plug_prime, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { + tcpm_log(port, "Failed to register plug SVID 0x%04x", + modep->altmode_desc[i].svid); + altmode = NULL; + } + port->plug_prime_altmode[i] = altmode; + } +} + #define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header) +#define supports_modal_cable(port) PD_IDH_MODAL_SUPP((port)->cable_ident.id_header) +#define supports_host(port) PD_IDH_HOST_SUPP((port->partner_ident.id_header)) + +/* + * Helper to determine whether the port is capable of SOP' communication at the + * current point in time. + */ +static bool tcpm_can_communicate_sop_prime(struct tcpm_port *port) +{ + /* Check to see if tcpc supports SOP' communication */ + if (!port->tcpc->cable_comm_capable || !port->tcpc->cable_comm_capable(port->tcpc)) + return false; + /* + * Power Delivery 2.0 Section 6.3.11 + * Before communicating with a Cable Plug a Port Should ensure that it + * is the Vconn Source and that the Cable Plugs are powered by + * performing a Vconn swap if necessary. Since it cannot be guaranteed + * that the present Vconn Source is supplying Vconn, the only means to + * ensure that the Cable Plugs are powered is for a Port wishing to + * communicate with a Cable Plug is to become the Vconn Source. + * + * Power Delivery 3.0 Section 6.3.11 + * Before communicating with a Cable Plug a Port Shall ensure that it + * is the Vconn source. + */ + if (port->vconn_role != TYPEC_SOURCE) + return false; + /* + * Power Delivery 2.0 Section 2.4.4 + * When no Contract or an Implicit Contract is in place the Source can + * communicate with a Cable Plug using SOP' packets in order to discover + * its characteristics. + * + * Power Delivery 3.0 Section 2.4.4 + * When no Contract or an Implicit Contract is in place only the Source + * port that is supplying Vconn is allowed to send packets to a Cable + * Plug and is allowed to respond to packets from the Cable Plug. + */ + if (!port->explicit_contract) + return port->pwr_role == TYPEC_SOURCE; + if (port->negotiated_rev == PD_REV30) + return true; + /* + * Power Delivery 2.0 Section 2.4.4 + * + * When an Explicit Contract is in place the DFP (either the Source or + * the Sink) can communicate with the Cable Plug(s) using SOP’/SOP” + * Packets (see Figure 2-3). + */ + if (port->negotiated_rev == PD_REV20) + return port->data_role == TYPEC_HOST; + return false; +} + +static bool tcpm_attempt_vconn_swap_discovery(struct tcpm_port *port) +{ + if (!port->tcpc->attempt_vconn_swap_discovery) + return false; + + /* Port is already source, no need to perform swap */ + if (port->vconn_role == TYPEC_SOURCE) + return false; + + /* + * Partner needs to support Alternate Modes with modal support. If + * partner is also capable of being a USB Host, it could be a device + * that supports Alternate Modes as the DFP. + */ + if (!supports_modal(port) || supports_host(port)) + return false; + + if ((port->negotiated_rev == PD_REV20 && port->data_role == TYPEC_HOST) || + port->negotiated_rev == PD_REV30) + return port->tcpc->attempt_vconn_swap_discovery(port->tcpc); + + return false; +} + + +static bool tcpm_cable_vdm_supported(struct tcpm_port *port) +{ + return !IS_ERR_OR_NULL(port->cable) && + typec_cable_is_active(port->cable) && + supports_modal_cable(port) && + tcpm_can_communicate_sop_prime(port); +} static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, const u32 *p, int cnt, u32 *response, - enum adev_actions *adev_action) + enum adev_actions *adev_action, + enum tcpm_transmit_type rx_sop_type, + enum tcpm_transmit_type *response_tx_sop_type) { struct typec_port *typec = port->typec_port; - struct typec_altmode *pdev; - struct pd_mode_data *modep; + struct typec_altmode *pdev, *pdev_prime; + struct pd_mode_data *modep, *modep_prime; int svdm_version; int rlen = 0; int cmd_type; int cmd; int i; + int ret; cmd_type = PD_VDO_CMDT(p[0]); cmd = PD_VDO_CMD(p[0]); @@ -1627,9 +1883,40 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX, PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); - svdm_version = typec_get_negotiated_svdm_version(typec); - if (svdm_version < 0) - return 0; + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + modep_prime = &port->mode_data_prime; + pdev_prime = typec_match_altmode(port->plug_prime_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + if (!IS_ERR_OR_NULL(port->cable)) { + svdm_version = typec_get_cable_svdm_version(typec); + if (PD_VDO_SVDM_VER(p[0]) < svdm_version) + typec_cable_set_svdm_version(port->cable, svdm_version); + } + break; + case TCPC_TX_SOP: + modep = &port->mode_data; + pdev = typec_match_altmode(port->partner_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + svdm_version = typec_get_negotiated_svdm_version(typec); + if (svdm_version < 0) + return 0; + break; + default: + modep = &port->mode_data; + pdev = typec_match_altmode(port->partner_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + svdm_version = typec_get_negotiated_svdm_version(typec); + if (svdm_version < 0) + return 0; + break; + } switch (cmd_type) { case CMDT_INIT: @@ -1699,55 +1986,186 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, (VDO_SVDM_VERS(typec_get_negotiated_svdm_version(typec))); break; case CMDT_RSP_ACK: - /* silently drop message if we are not connected */ - if (IS_ERR_OR_NULL(port->partner)) + /* + * Silently drop message if we are not connected, but can process + * if SOP' Discover Identity prior to explicit contract. + */ + if (IS_ERR_OR_NULL(port->partner) && + !(rx_sop_type == TCPC_TX_SOP_PRIME && cmd == CMD_DISCOVER_IDENT)) break; tcpm_ams_finish(port); switch (cmd) { + /* + * SVDM Command Flow for SOP and SOP': + * SOP Discover Identity + * SOP' Discover Identity + * SOP Discover SVIDs + * Discover Modes + * (Active Cables) + * SOP' Discover SVIDs + * Discover Modes + * + * Perform Discover SOP' if the port can communicate with cable + * plug. + */ case CMD_DISCOVER_IDENT: - if (PD_VDO_SVDM_VER(p[0]) < svdm_version) - typec_partner_set_svdm_version(port->partner, - PD_VDO_SVDM_VER(p[0])); - /* 6.4.4.3.1 */ - svdm_consume_identity(port, p, cnt); - response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version(typec), - CMD_DISCOVER_SVID); - rlen = 1; + switch (rx_sop_type) { + case TCPC_TX_SOP: + if (PD_VDO_SVDM_VER(p[0]) < svdm_version) { + typec_partner_set_svdm_version(port->partner, + PD_VDO_SVDM_VER(p[0])); + /* If cable is discovered before partner, downgrade svdm */ + if (!IS_ERR_OR_NULL(port->cable) && + (typec_get_cable_svdm_version(port->typec_port) > + svdm_version)) + typec_cable_set_svdm_version(port->cable, + svdm_version); + } + /* 6.4.4.3.1 */ + svdm_consume_identity(port, p, cnt); + /* Attempt Vconn swap, delay SOP' discovery if necessary */ + if (tcpm_attempt_vconn_swap_discovery(port)) { + port->send_discover_prime = true; + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (!ret) + return 0; + /* Cannot perform Vconn swap */ + port->upcoming_state = INVALID_STATE; + port->send_discover_prime = false; + } + + /* + * Attempt Discover Identity on SOP' if the + * cable was not discovered previously, and use + * the SVDM version of the partner to probe. + */ + if (IS_ERR_OR_NULL(port->cable) && + tcpm_can_communicate_sop_prime(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + port->send_discover_prime = true; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_IDENT); + rlen = 1; + } else { + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_SVID); + rlen = 1; + } + break; + case TCPC_TX_SOP_PRIME: + /* + * svdm_consume_identity_sop_prime will determine + * the svdm_version for the cable moving forward. + */ + svdm_consume_identity_sop_prime(port, p, cnt); + + /* + * If received in SRC_VDM_IDENTITY_REQUEST, continue + * to SRC_SEND_CAPABILITIES + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + return 0; + } + + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_SVID); + rlen = 1; + break; + default: + return 0; + } break; case CMD_DISCOVER_SVID: + *response_tx_sop_type = rx_sop_type; /* 6.4.4.3.2 */ - if (svdm_consume_svids(port, p, cnt)) { + if (svdm_consume_svids(port, p, cnt, rx_sop_type)) { response[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); rlen = 1; - } else if (modep->nsvids && supports_modal(port)) { - response[0] = VDO(modep->svids[0], 1, svdm_version, - CMD_DISCOVER_MODES); - rlen = 1; + } else { + if (rx_sop_type == TCPC_TX_SOP) { + if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (modep_prime->nsvids) { + response[0] = VDO(modep_prime->svids[0], 1, + svdm_version, CMD_DISCOVER_MODES); + rlen = 1; + } + } } break; case CMD_DISCOVER_MODES: - /* 6.4.4.3.3 */ - svdm_consume_modes(port, p, cnt); - modep->svid_index++; - if (modep->svid_index < modep->nsvids) { - u16 svid = modep->svids[modep->svid_index]; - response[0] = VDO(svid, 1, svdm_version, CMD_DISCOVER_MODES); - rlen = 1; - } else { - tcpm_register_partner_altmodes(port); + if (rx_sop_type == TCPC_TX_SOP) { + /* 6.4.4.3.3 */ + svdm_consume_modes(port, p, cnt, rx_sop_type); + modep->svid_index++; + if (modep->svid_index < modep->nsvids) { + u16 svid = modep->svids[modep->svid_index]; + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(svid, 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; + } else if (tcpm_cable_vdm_supported(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + response[0] = VDO(USB_SID_PD, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_SVID); + rlen = 1; + } else { + tcpm_register_partner_altmodes(port); + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + /* 6.4.4.3.3 */ + svdm_consume_modes(port, p, cnt, rx_sop_type); + modep_prime->svid_index++; + if (modep_prime->svid_index < modep_prime->nsvids) { + u16 svid = modep_prime->svids[modep_prime->svid_index]; + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + response[0] = VDO(svid, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_MODES); + rlen = 1; + } else { + tcpm_register_plug_altmodes(port); + tcpm_register_partner_altmodes(port); + } } break; case CMD_ENTER_MODE: - if (adev && pdev) - *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; + *response_tx_sop_type = rx_sop_type; + if (rx_sop_type == TCPC_TX_SOP) { + if (adev && pdev) { + typec_altmode_update_active(pdev, true); + *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (adev && pdev_prime) { + typec_altmode_update_active(pdev_prime, true); + *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; + } + } return 0; case CMD_EXIT_MODE: - if (adev && pdev) { - /* Back to USB Operation */ - *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM; - return 0; + *response_tx_sop_type = rx_sop_type; + if (rx_sop_type == TCPC_TX_SOP) { + if (adev && pdev) { + typec_altmode_update_active(pdev, false); + /* Back to USB Operation */ + *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM; + return 0; + } } break; case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): @@ -1800,13 +2218,15 @@ static void tcpm_pd_handle_msg(struct tcpm_port *port, enum tcpm_ams ams); static void tcpm_handle_vdm_request(struct tcpm_port *port, - const __le32 *payload, int cnt) + const __le32 *payload, int cnt, + enum tcpm_transmit_type rx_sop_type) { enum adev_actions adev_action = ADEV_NONE; struct typec_altmode *adev; u32 p[PD_MAX_PAYLOAD]; u32 response[8] = { }; int i, rlen = 0; + enum tcpm_transmit_type response_tx_sop_type = TCPC_TX_SOP; for (i = 0; i < cnt; i++) p[i] = le32_to_cpu(payload[i]); @@ -1841,7 +2261,8 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, * - We will send NAK and the flag will be cleared in the state machine. */ port->vdm_sm_running = true; - rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action); + rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action, + rx_sop_type, &response_tx_sop_type); } else { if (port->negotiated_rev >= PD_REV30) tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); @@ -1877,19 +2298,37 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, typec_altmode_vdm(adev, p[0], &p[1], cnt); break; case ADEV_QUEUE_VDM: - typec_altmode_vdm(adev, p[0], &p[1], cnt); + if (response_tx_sop_type == TCPC_TX_SOP_PRIME) + typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P, p[0], &p[1], cnt); + else + typec_altmode_vdm(adev, p[0], &p[1], cnt); break; case ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL: - if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { - int svdm_version = typec_get_negotiated_svdm_version( - port->typec_port); - if (svdm_version < 0) - break; - - response[0] = VDO(adev->svid, 1, svdm_version, - CMD_EXIT_MODE); - response[0] |= VDO_OPOS(adev->mode); - rlen = 1; + if (response_tx_sop_type == TCPC_TX_SOP_PRIME) { + if (typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P, + p[0], &p[1], cnt)) { + int svdm_version = typec_get_cable_svdm_version( + port->typec_port); + if (svdm_version < 0) + break; + + response[0] = VDO(adev->svid, 1, svdm_version, + CMD_EXIT_MODE); + response[0] |= VDO_OPOS(adev->mode); + rlen = 1; + } + } else { + if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { + int svdm_version = typec_get_negotiated_svdm_version( + port->typec_port); + if (svdm_version < 0) + break; + + response[0] = VDO(adev->svid, 1, svdm_version, + CMD_EXIT_MODE); + response[0] |= VDO_OPOS(adev->mode); + rlen = 1; + } } break; case ADEV_ATTENTION: @@ -1909,19 +2348,38 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, mutex_lock(&port->lock); if (rlen > 0) - tcpm_queue_vdm(port, response[0], &response[1], rlen - 1); + tcpm_queue_vdm(port, response[0], &response[1], rlen - 1, response_tx_sop_type); else port->vdm_sm_running = false; } static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, - const u32 *data, int count) + const u32 *data, int count, enum tcpm_transmit_type tx_sop_type) { - int svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + int svdm_version; u32 header; - if (svdm_version < 0) - return; + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + /* + * If the port partner is discovered, then the port partner's + * SVDM Version will be returned + */ + svdm_version = typec_get_cable_svdm_version(port->typec_port); + if (svdm_version < 0) + svdm_version = SVDM_VER_MAX; + break; + case TCPC_TX_SOP: + svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + if (svdm_version < 0) + return; + break; + default: + svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + if (svdm_version < 0) + return; + break; + } if (WARN_ON(count > VDO_MAX_SIZE - 1)) count = VDO_MAX_SIZE - 1; @@ -1930,7 +2388,7 @@ static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), svdm_version, cmd); - tcpm_queue_vdm(port, header, data, count); + tcpm_queue_vdm(port, header, data, count, tx_sop_type); } static unsigned int vdm_ready_timeout(u32 vdm_hdr) @@ -1964,6 +2422,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) struct pd_message msg; int i, res = 0; u32 vdo_hdr = port->vdo_data[0]; + u32 response[8] = { }; switch (port->vdm_state) { case VDM_STATE_READY: @@ -1977,7 +2436,8 @@ static void vdm_run_state_machine(struct tcpm_port *port) * if there's traffic or we're not in PDO ready state don't send * a VDM. */ - if (port->state != SRC_READY && port->state != SNK_READY) { + if (port->state != SRC_READY && port->state != SNK_READY && + port->state != SRC_VDM_IDENTITY_REQUEST) { port->vdm_sm_running = false; break; } @@ -1988,7 +2448,17 @@ static void vdm_run_state_machine(struct tcpm_port *port) case CMD_DISCOVER_IDENT: res = tcpm_ams_start(port, DISCOVER_IDENTITY); if (res == 0) { - port->send_discover = false; + switch (port->tx_sop_type) { + case TCPC_TX_SOP_PRIME: + port->send_discover_prime = false; + break; + case TCPC_TX_SOP: + port->send_discover = false; + break; + default: + port->send_discover = false; + break; + } } else if (res == -EAGAIN) { port->vdo_data[0] = 0; mod_send_discover_delayed_work(port, @@ -2044,12 +2514,21 @@ static void vdm_run_state_machine(struct tcpm_port *port) break; case VDM_STATE_ERR_SEND: /* + * When sending Discover Identity to SOP' before establishing an + * explicit contract, do not retry. Instead, weave sending + * Source_Capabilities over SOP and Discover Identity over SOP'. + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_ams_finish(port); + port->vdm_state = VDM_STATE_DONE; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + /* * A partner which does not support USB PD will not reply, * so this is not a fatal error. At the same time, some * devices may not return GoodCRC under some circumstances, * so we need to retry. */ - if (port->vdm_retries < 3) { + } else if (port->vdm_retries < 3) { tcpm_log(port, "VDM Tx error, retry"); port->vdm_retries++; port->vdm_state = VDM_STATE_READY; @@ -2057,19 +2536,59 @@ static void vdm_run_state_machine(struct tcpm_port *port) tcpm_ams_finish(port); } else { tcpm_ams_finish(port); + if (port->tx_sop_type == TCPC_TX_SOP) + break; + /* Handle SOP' Transmission Errors */ + switch (PD_VDO_CMD(vdo_hdr)) { + /* + * If Discover Identity fails on SOP', then resume + * discovery process on SOP only. + */ + case CMD_DISCOVER_IDENT: + port->vdo_data[0] = 0; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version( + port->typec_port), + CMD_DISCOVER_SVID); + tcpm_queue_vdm(port, response[0], &response[1], + 0, TCPC_TX_SOP); + break; + /* + * If Discover SVIDs or Discover Modes fail, then + * proceed with Alt Mode discovery process on SOP. + */ + case CMD_DISCOVER_SVID: + tcpm_register_partner_altmodes(port); + break; + case CMD_DISCOVER_MODES: + tcpm_register_partner_altmodes(port); + break; + default: + break; + } } break; case VDM_STATE_SEND_MESSAGE: /* Prepare and send VDM */ memset(&msg, 0, sizeof(msg)); - msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, - port->pwr_role, - port->data_role, - port->negotiated_rev, - port->message_id, port->vdo_count); + if (port->tx_sop_type == TCPC_TX_SOP_PRIME) { + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + 0, /* Cable Plug Indicator for DFP/UFP */ + 0, /* Reserved */ + port->negotiated_rev_prime, + port->message_id_prime, + port->vdo_count); + } else { + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + port->vdo_count); + } for (i = 0; i < port->vdo_count; i++) msg.payload[i] = cpu_to_le32(port->vdo_data[i]); - res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + res = tcpm_pd_transmit(port, port->tx_sop_type, &msg); if (res < 0) { port->vdm_state = VDM_STATE_ERR_SEND; } else { @@ -2244,7 +2763,7 @@ static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo) header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE); header |= VDO_OPOS(altmode->mode); - tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0); + tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP); return 0; } @@ -2261,7 +2780,7 @@ static int tcpm_altmode_exit(struct typec_altmode *altmode) header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); header |= VDO_OPOS(altmode->mode); - tcpm_queue_vdm_unlocked(port, header, NULL, 0); + tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP); return 0; } @@ -2270,7 +2789,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); + tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP); return 0; } @@ -2281,6 +2800,58 @@ static const struct typec_altmode_ops tcpm_altmode_ops = { .vdm = tcpm_altmode_vdm, }; + +static int tcpm_cable_altmode_enter(struct typec_altmode *altmode, enum typec_plug_index sop, + u32 *vdo) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; + u32 header; + + svdm_version = typec_get_cable_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP_PRIME); + return 0; +} + +static int tcpm_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plug_index sop) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; + u32 header; + + svdm_version = typec_get_cable_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP_PRIME); + return 0; +} + +static int tcpm_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug_index sop, + u32 header, const u32 *data, int count) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + + tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP_PRIME); + + return 0; +} + +static const struct typec_cable_ops tcpm_cable_ops = { + .enter = tcpm_cable_altmode_enter, + .exit = tcpm_cable_altmode_exit, + .vdm = tcpm_cable_altmode_vdm, +}; + /* * PD (data, control) command handling functions */ @@ -2293,7 +2864,8 @@ static inline enum tcpm_state ready_state(struct tcpm_port *port) } static int tcpm_pd_send_control(struct tcpm_port *port, - enum pd_ctrl_msg_type type); + enum pd_ctrl_msg_type type, + enum tcpm_transmit_type tx_sop_type); static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload, int cnt) @@ -2455,7 +3027,8 @@ static int tcpm_register_sink_caps(struct tcpm_port *port) } static void tcpm_pd_data_request(struct tcpm_port *port, - const struct pd_message *msg) + const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) { enum pd_data_msg_type type = pd_header_type_le(msg->header); unsigned int cnt = pd_header_cnt_le(msg->header); @@ -2496,8 +3069,11 @@ static void tcpm_pd_data_request(struct tcpm_port *port, break; } - if (rev < PD_MAX_REV) + if (rev < PD_MAX_REV) { port->negotiated_rev = rev; + if (port->negotiated_rev_prime > port->negotiated_rev) + port->negotiated_rev_prime = port->negotiated_rev; + } if (port->pwr_role == TYPEC_SOURCE) { if (port->ams == GET_SOURCE_CAPABILITIES) @@ -2548,8 +3124,11 @@ static void tcpm_pd_data_request(struct tcpm_port *port, break; } - if (rev < PD_MAX_REV) + if (rev < PD_MAX_REV) { port->negotiated_rev = rev; + if (port->negotiated_rev_prime > port->negotiated_rev) + port->negotiated_rev_prime = port->negotiated_rev; + } if (port->pwr_role != TYPEC_SOURCE || cnt != 1) { tcpm_pd_handle_msg(port, @@ -2605,7 +3184,7 @@ static void tcpm_pd_data_request(struct tcpm_port *port, NONE_AMS); break; case PD_DATA_VENDOR_DEF: - tcpm_handle_vdm_request(port, msg->payload, cnt); + tcpm_handle_vdm_request(port, msg->payload, cnt, rx_sop_type); break; case PD_DATA_BIST: port->bist_request = le32_to_cpu(msg->payload[0]); @@ -2647,10 +3226,12 @@ static void tcpm_pps_complete(struct tcpm_port *port, int result) } static void tcpm_pd_ctrl_request(struct tcpm_port *port, - const struct pd_message *msg) + const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) { enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); enum tcpm_state next_state; + unsigned int rev = pd_header_rev_le(msg->header); /* * Stop VDM state machine if interrupted by other Messages while NOT_SUPP is allowed in @@ -2815,6 +3396,16 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case SOFT_RESET_SEND: if (port->ams == SOFT_RESET_AMS) tcpm_ams_finish(port); + /* + * SOP' Soft Reset is done after Vconn Swap, + * which returns to ready state + */ + if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (rev < port->negotiated_rev_prime) + port->negotiated_rev_prime = rev; + tcpm_set_state(port, ready_state(port), 0); + break; + } if (port->pwr_role == TYPEC_SOURCE) { port->upcoming_state = SRC_SEND_CAPABILITIES; tcpm_ams_start(port, POWER_NEGOTIATION); @@ -2981,6 +3572,7 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) const struct pd_message *msg = &event->msg; unsigned int cnt = pd_header_cnt_le(msg->header); struct tcpm_port *port = event->port; + enum tcpm_transmit_type rx_sop_type = event->rx_sop_type; mutex_lock(&port->lock); @@ -2992,6 +3584,14 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) unsigned int msgid = pd_header_msgid_le(msg->header); /* + * Drop SOP' messages if cannot receive via + * tcpm_can_communicate_sop_prime + */ + if (rx_sop_type == TCPC_TX_SOP_PRIME && + !tcpm_can_communicate_sop_prime(port)) + goto done; + + /* * USB PD standard, 6.6.1.2: * "... if MessageID value in a received Message is the * same as the stored value, the receiver shall return a @@ -3000,16 +3600,26 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) * Message). Note: this shall not apply to the Soft_Reset * Message which always has a MessageID value of zero." */ - if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET) - goto done; - port->rx_msgid = msgid; + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + if (msgid == port->rx_msgid_prime) + goto done; + port->rx_msgid_prime = msgid; + break; + case TCPC_TX_SOP: + default: + if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET) + goto done; + port->rx_msgid = msgid; + break; + } /* * If both ends believe to be DFP/host, we have a data role * mismatch. */ if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) == - (port->data_role == TYPEC_HOST)) { + (port->data_role == TYPEC_HOST) && rx_sop_type == TCPC_TX_SOP) { tcpm_log(port, "Data role mismatch, initiating error recovery"); tcpm_set_state(port, ERROR_RECOVERY, 0); @@ -3017,9 +3627,9 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) if (le16_to_cpu(msg->header) & PD_HEADER_EXT_HDR) tcpm_pd_ext_msg_request(port, msg); else if (cnt) - tcpm_pd_data_request(port, msg); + tcpm_pd_data_request(port, msg, rx_sop_type); else - tcpm_pd_ctrl_request(port, msg); + tcpm_pd_ctrl_request(port, msg, rx_sop_type); } } @@ -3028,7 +3638,8 @@ done: kfree(event); } -void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg) +void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) { struct pd_rx_event *event; @@ -3038,23 +3649,47 @@ void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg) kthread_init_work(&event->work, tcpm_pd_rx_handler); event->port = port; + event->rx_sop_type = rx_sop_type; memcpy(&event->msg, msg, sizeof(*msg)); kthread_queue_work(port->wq, &event->work); } EXPORT_SYMBOL_GPL(tcpm_pd_receive); static int tcpm_pd_send_control(struct tcpm_port *port, - enum pd_ctrl_msg_type type) + enum pd_ctrl_msg_type type, + enum tcpm_transmit_type tx_sop_type) { struct pd_message msg; memset(&msg, 0, sizeof(msg)); - msg.header = PD_HEADER_LE(type, port->pwr_role, - port->data_role, - port->negotiated_rev, - port->message_id, 0); + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + msg.header = PD_HEADER_LE(type, + 0, /* Cable Plug Indicator for DFP/UFP */ + 0, /* Reserved */ + port->negotiated_rev, + port->message_id_prime, + 0); + break; + case TCPC_TX_SOP: + msg.header = PD_HEADER_LE(type, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 0); + break; + default: + msg.header = PD_HEADER_LE(type, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 0); + break; + } - return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + return tcpm_pd_transmit(port, tx_sop_type, &msg); } /* @@ -3073,13 +3708,13 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) switch (queued_message) { case PD_MSG_CTRL_WAIT: - tcpm_pd_send_control(port, PD_CTRL_WAIT); + tcpm_pd_send_control(port, PD_CTRL_WAIT, TCPC_TX_SOP); break; case PD_MSG_CTRL_REJECT: - tcpm_pd_send_control(port, PD_CTRL_REJECT); + tcpm_pd_send_control(port, PD_CTRL_REJECT, TCPC_TX_SOP); break; case PD_MSG_CTRL_NOT_SUPP: - tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP); break; case PD_MSG_DATA_SINK_CAP: ret = tcpm_pd_send_sink_caps(port); @@ -3649,6 +4284,7 @@ static int tcpm_src_attach(struct tcpm_port *port) port->attached = true; port->send_discover = true; + port->send_discover_prime = false; return 0; @@ -3665,6 +4301,15 @@ out_disable_mux: static void tcpm_typec_disconnect(struct tcpm_port *port) { + /* + * Unregister plug/cable outside of port->connected because cable can + * be discovered before SRC_READY/SNK_READY states where port->connected + * is set. + */ + typec_unregister_plug(port->plug_prime); + typec_unregister_cable(port->cable); + port->plug_prime = NULL; + port->cable = NULL; if (port->connected) { typec_partner_set_usb_power_delivery(port->partner, NULL); typec_unregister_partner(port->partner); @@ -3676,14 +4321,20 @@ static void tcpm_typec_disconnect(struct tcpm_port *port) static void tcpm_unregister_altmodes(struct tcpm_port *port) { struct pd_mode_data *modep = &port->mode_data; + struct pd_mode_data *modep_prime = &port->mode_data_prime; int i; for (i = 0; i < modep->altmodes; i++) { typec_unregister_altmode(port->partner_altmode[i]); port->partner_altmode[i] = NULL; } + for (i = 0; i < modep_prime->altmodes; i++) { + typec_unregister_altmode(port->plug_prime_altmode[i]); + port->plug_prime_altmode[i] = NULL; + } memset(modep, 0, sizeof(*modep)); + memset(modep_prime, 0, sizeof(*modep_prime)); } static void tcpm_set_partner_usb_comm_capable(struct tcpm_port *port, bool capable) @@ -3712,6 +4363,7 @@ static void tcpm_reset_port(struct tcpm_port *port) * we can check tcpm_pd_rx_handler() if we had seen it before. */ port->rx_msgid = -1; + port->rx_msgid_prime = -1; port->tcpc->set_pd_rx(port->tcpc, false); tcpm_init_vbus(port); /* also disables charging */ @@ -3786,6 +4438,7 @@ static int tcpm_snk_attach(struct tcpm_port *port) port->attached = true; port->send_discover = true; + port->send_discover_prime = false; return 0; } @@ -4026,8 +4679,11 @@ static void run_state_machine(struct tcpm_port *port) port->pwr_opmode = TYPEC_PWR_MODE_USB; port->caps_count = 0; port->negotiated_rev = PD_MAX_REV; + port->negotiated_rev_prime = PD_MAX_REV; port->message_id = 0; + port->message_id_prime = 0; port->rx_msgid = -1; + port->rx_msgid_prime = -1; port->explicit_contract = false; /* SNK -> SRC POWER/FAST_ROLE_SWAP finished */ if (port->ams == POWER_ROLE_SWAP || @@ -4048,8 +4704,12 @@ static void run_state_machine(struct tcpm_port *port) } ret = tcpm_pd_send_source_caps(port); if (ret < 0) { - tcpm_set_state(port, SRC_SEND_CAPABILITIES, - PD_T_SEND_SOURCE_CAP); + if (tcpm_can_communicate_sop_prime(port) && + IS_ERR_OR_NULL(port->cable)) + tcpm_set_state(port, SRC_VDM_IDENTITY_REQUEST, 0); + else + tcpm_set_state(port, SRC_SEND_CAPABILITIES, + PD_T_SEND_SOURCE_CAP); } else { /* * Per standard, we should clear the reset counter here. @@ -4090,7 +4750,7 @@ static void run_state_machine(struct tcpm_port *port) case SRC_NEGOTIATE_CAPABILITIES: ret = tcpm_pd_check_request(port); if (ret < 0) { - tcpm_pd_send_control(port, PD_CTRL_REJECT); + tcpm_pd_send_control(port, PD_CTRL_REJECT, TCPC_TX_SOP); if (!port->explicit_contract) { tcpm_set_state(port, SRC_WAIT_NEW_CAPABILITIES, 0); @@ -4098,7 +4758,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SRC_READY, 0); } } else { - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_set_partner_usb_comm_capable(port, !!(port->sink_request & RDO_USB_COMM)); tcpm_set_state(port, SRC_TRANSITION_SUPPLY, @@ -4107,7 +4767,7 @@ static void run_state_machine(struct tcpm_port *port) break; case SRC_TRANSITION_SUPPLY: /* XXX: regulator_set_voltage(vbus, ...) */ - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); port->explicit_contract = true; typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); port->pwr_opmode = TYPEC_PWR_MODE_PD; @@ -4144,14 +4804,23 @@ static void run_state_machine(struct tcpm_port *port) * 6.4.4.3.1 Discover Identity * "The Discover Identity Command Shall only be sent to SOP when there is an * Explicit Contract." - * For now, this driver only supports SOP for DISCOVER_IDENTITY, thus using - * port->explicit_contract to decide whether to send the command. + * + * Discover Identity on SOP' should be discovered prior to the + * ready state, but if done after a Vconn Swap following Discover + * Identity on SOP then the discovery process can be run here + * as well. */ if (port->explicit_contract) { - tcpm_set_initial_svdm_version(port); + if (port->send_discover_prime) { + port->tx_sop_type = TCPC_TX_SOP_PRIME; + } else { + port->tx_sop_type = TCPC_TX_SOP; + tcpm_set_initial_svdm_version(port); + } mod_send_discover_delayed_work(port, 0); } else { port->send_discover = false; + port->send_discover_prime = false; } /* @@ -4267,8 +4936,11 @@ static void run_state_machine(struct tcpm_port *port) typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; port->negotiated_rev = PD_MAX_REV; + port->negotiated_rev_prime = PD_MAX_REV; port->message_id = 0; + port->message_id_prime = 0; port->rx_msgid = -1; + port->rx_msgid_prime = -1; port->explicit_contract = false; if (port->ams == POWER_ROLE_SWAP || @@ -4440,14 +5112,23 @@ static void run_state_machine(struct tcpm_port *port) * 6.4.4.3.1 Discover Identity * "The Discover Identity Command Shall only be sent to SOP when there is an * Explicit Contract." - * For now, this driver only supports SOP for DISCOVER_IDENTITY, thus using - * port->explicit_contract. + * + * Discover Identity on SOP' should be discovered prior to the + * ready state, but if done after a Vconn Swap following Discover + * Identity on SOP then the discovery process can be run here + * as well. */ if (port->explicit_contract) { - tcpm_set_initial_svdm_version(port); + if (port->send_discover_prime) { + port->tx_sop_type = TCPC_TX_SOP_PRIME; + } else { + port->tx_sop_type = TCPC_TX_SOP; + tcpm_set_initial_svdm_version(port); + } mod_send_discover_delayed_work(port, 0); } else { port->send_discover = false; + port->send_discover_prime = false; } power_supply_changed(port->psy); @@ -4488,6 +5169,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_unregister_altmodes(port); port->nr_sink_caps = 0; port->send_discover = true; + port->send_discover_prime = false; if (port->pwr_role == TYPEC_SOURCE) tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, PD_T_PS_HARD_RESET); @@ -4589,7 +5271,7 @@ static void run_state_machine(struct tcpm_port *port) /* remove existing capabilities */ usb_power_delivery_unregister_capabilities(port->partner_source_caps); port->partner_source_caps = NULL; - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_ams_finish(port); if (port->pwr_role == TYPEC_SOURCE) { port->upcoming_state = SRC_SEND_CAPABILITIES; @@ -4606,35 +5288,53 @@ static void run_state_machine(struct tcpm_port *port) tcpm_ams_start(port, SOFT_RESET_AMS); break; case SOFT_RESET_SEND: - port->message_id = 0; - port->rx_msgid = -1; - /* remove existing capabilities */ - usb_power_delivery_unregister_capabilities(port->partner_source_caps); - port->partner_source_caps = NULL; - if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET)) - tcpm_set_state_cond(port, hard_reset_state(port), 0); - else - tcpm_set_state_cond(port, hard_reset_state(port), - PD_T_SENDER_RESPONSE); + /* + * Power Delivery 3.0 Section 6.3.13 + * + * A Soft_Reset Message Shall be targeted at a specific entity + * depending on the type of SOP* packet used. + */ + if (port->tx_sop_type == TCPC_TX_SOP_PRIME) { + port->message_id_prime = 0; + port->rx_msgid_prime = -1; + tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP_PRIME); + tcpm_set_state_cond(port, ready_state(port), PD_T_SENDER_RESPONSE); + } else { + port->message_id = 0; + port->rx_msgid = -1; + /* remove existing capabilities */ + usb_power_delivery_unregister_capabilities(port->partner_source_caps); + port->partner_source_caps = NULL; + if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP)) + tcpm_set_state_cond(port, hard_reset_state(port), 0); + else + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + } break; /* DR_Swap states */ case DR_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_DR_SWAP); - if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); + if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { port->send_discover = true; + port->send_discover_prime = false; + } tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; case DR_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); - if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); + if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { port->send_discover = true; + port->send_discover_prime = false; + } tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); break; case DR_SWAP_SEND_TIMEOUT: tcpm_swap_complete(port, -ETIMEDOUT); port->send_discover = false; + port->send_discover_prime = false; tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); break; @@ -4651,7 +5351,7 @@ static void run_state_machine(struct tcpm_port *port) break; case FR_SWAP_SEND: - if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP)) { + if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP, TCPC_TX_SOP)) { tcpm_set_state(port, ERROR_RECOVERY, 0); break; } @@ -4671,7 +5371,7 @@ static void run_state_machine(struct tcpm_port *port) 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)) { + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP)) { tcpm_set_state(port, ERROR_RECOVERY, 0); break; } @@ -4681,11 +5381,11 @@ static void run_state_machine(struct tcpm_port *port) /* PR_Swap states */ case PR_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_set_state(port, PR_SWAP_START, 0); break; case PR_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_PR_SWAP); + tcpm_pd_send_control(port, PD_CTRL_PR_SWAP, TCPC_TX_SOP); tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; @@ -4727,7 +5427,7 @@ static void run_state_machine(struct tcpm_port *port) * supply is turned off" */ tcpm_set_pwr_role(port, TYPEC_SINK); - if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) { + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP)) { tcpm_set_state(port, ERROR_RECOVERY, 0); break; } @@ -4774,17 +5474,17 @@ static void run_state_machine(struct tcpm_port *port) * Source." */ tcpm_set_pwr_role(port, TYPEC_SOURCE); - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START); break; case VCONN_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_ams_finish(port); tcpm_set_state(port, VCONN_SWAP_START, 0); break; case VCONN_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP); + tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP, TCPC_TX_SOP); tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; @@ -4803,14 +5503,34 @@ static void run_state_machine(struct tcpm_port *port) PD_T_VCONN_SOURCE_ON); break; case VCONN_SWAP_TURN_ON_VCONN: - tcpm_set_vconn(port, true); - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); - tcpm_set_state(port, ready_state(port), 0); + ret = tcpm_set_vconn(port, true); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); + /* + * USB PD 3.0 Section 6.4.4.3.1 + * + * Note that a Cable Plug or VPD will not be ready for PD + * Communication until tVCONNStable after VCONN has been applied + */ + if (!ret) + tcpm_set_state(port, VCONN_SWAP_SEND_SOFT_RESET, + PD_T_VCONN_STABLE); + else + tcpm_set_state(port, ready_state(port), 0); break; case VCONN_SWAP_TURN_OFF_VCONN: tcpm_set_vconn(port, false); tcpm_set_state(port, ready_state(port), 0); break; + case VCONN_SWAP_SEND_SOFT_RESET: + tcpm_swap_complete(port, port->swap_status); + if (tcpm_can_communicate_sop_prime(port)) { + port->tx_sop_type = TCPC_TX_SOP_PRIME; + port->upcoming_state = SOFT_RESET_SEND; + tcpm_ams_start(port, SOFT_RESET_AMS); + } else { + tcpm_set_state(port, ready_state(port), 0); + } + break; case DR_SWAP_CANCEL: case PR_SWAP_CANCEL: @@ -4846,7 +5566,7 @@ static void run_state_machine(struct tcpm_port *port) } break; case GET_STATUS_SEND: - tcpm_pd_send_control(port, PD_CTRL_GET_STATUS); + tcpm_pd_send_control(port, PD_CTRL_GET_STATUS, TCPC_TX_SOP); tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; @@ -4854,7 +5574,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, ready_state(port), 0); break; case GET_PPS_STATUS_SEND: - tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS); + tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS, TCPC_TX_SOP); tcpm_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; @@ -4862,7 +5582,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, ready_state(port), 0); break; case GET_SINK_CAP: - tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP); + tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP, TCPC_TX_SOP); tcpm_set_state(port, GET_SINK_CAP_TIMEOUT, PD_T_SENDER_RESPONSE); break; case GET_SINK_CAP_TIMEOUT: @@ -4901,9 +5621,18 @@ static void run_state_machine(struct tcpm_port *port) /* Chunk state */ case CHUNK_NOT_SUPP: - tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP); tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? SRC_READY : SNK_READY, 0); break; + + /* Cable states */ + case SRC_VDM_IDENTITY_REQUEST: + port->send_discover_prime = true; + port->tx_sop_type = TCPC_TX_SOP_PRIME; + mod_send_discover_delayed_work(port, 0); + port->upcoming_state = SRC_SEND_CAPABILITIES; + break; + default: WARN(1, "Unexpected port state %d\n", port->state); break; @@ -5595,7 +6324,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work) goto unlock; /* Send when the state machine is idle */ - if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover) + if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover || + port->send_discover_prime) goto resched; port->upcoming_state = GET_SINK_CAP; @@ -5618,21 +6348,23 @@ static void tcpm_send_discover_work(struct kthread_work *work) mutex_lock(&port->lock); /* No need to send DISCOVER_IDENTITY anymore */ - if (!port->send_discover) + if (!port->send_discover && !port->send_discover_prime) goto unlock; if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { port->send_discover = false; + port->send_discover_prime = false; goto unlock; } /* Retry if the port is not idle */ - if ((port->state != SRC_READY && port->state != SNK_READY) || port->vdm_sm_running) { + if ((port->state != SRC_READY && port->state != SNK_READY && + port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) { mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); goto unlock; } - tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0); + tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type); unlock: mutex_unlock(&port->lock); @@ -6859,6 +7591,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) typec_port_register_altmodes(port->typec_port, &tcpm_altmode_ops, port, port->port_altmode, ALTMODE_DISCOVERY_MAX); + typec_port_register_cable_ops(port->port_altmode, ARRAY_SIZE(port->port_altmode), + &tcpm_cable_ops); port->registered = true; mutex_lock(&port->lock); diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c index 87d4abde0ea2..cf719307b3f6 100644 --- a/drivers/usb/typec/tcpm/wcove.c +++ b/drivers/usb/typec/tcpm/wcove.c @@ -535,7 +535,7 @@ static irqreturn_t wcove_typec_irq(int irq, void *data) goto err; } - tcpm_pd_receive(wcove->tcpm, &msg); + tcpm_pd_receive(wcove->tcpm, &msg, TCPC_TX_SOP); ret = regmap_read(wcove->regmap, USBC_RXSTATUS, &status); diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 449c125f6f87..dda7c7c94e08 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -192,6 +192,12 @@ struct ucsi_ccg_altmode { bool checked; } __packed; +#define CCGX_MESSAGE_IN_MAX 4 +struct op_region { + __le32 cci; + __le32 message_in[CCGX_MESSAGE_IN_MAX]; +}; + struct ucsi_ccg { struct device *dev; struct ucsi *ucsi; @@ -222,6 +228,13 @@ struct ucsi_ccg { bool has_multiple_dp; struct ucsi_ccg_altmode orig[UCSI_MAX_ALTMODES]; struct ucsi_ccg_altmode updated[UCSI_MAX_ALTMODES]; + + /* + * This spinlock protects op_data which includes CCI and MESSAGE_IN that + * will be updated in ISR + */ + spinlock_t op_lock; + struct op_region op_data; }; static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) @@ -305,12 +318,42 @@ static int ccg_write(struct ucsi_ccg *uc, u16 rab, const u8 *data, u32 len) return 0; } +static int ccg_op_region_update(struct ucsi_ccg *uc, u32 cci) +{ + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_MESSAGE_IN); + struct op_region *data = &uc->op_data; + unsigned char *buf; + size_t size = sizeof(data->message_in); + + buf = kzalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + if (UCSI_CCI_LENGTH(cci)) { + int ret = ccg_read(uc, reg, (void *)buf, size); + + if (ret) { + kfree(buf); + return ret; + } + } + + spin_lock(&uc->op_lock); + data->cci = cpu_to_le32(cci); + if (UCSI_CCI_LENGTH(cci)) + memcpy(&data->message_in, buf, size); + spin_unlock(&uc->op_lock); + kfree(buf); + return 0; +} + static int ucsi_ccg_init(struct ucsi_ccg *uc) { unsigned int count = 10; u8 data; int status; + spin_lock_init(&uc->op_lock); + data = CCGX_RAB_UCSI_CONTROL_STOP; status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data)); if (status < 0) @@ -520,9 +563,20 @@ static int ucsi_ccg_read(struct ucsi *ucsi, unsigned int offset, u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset); struct ucsi_capability *cap; struct ucsi_altmode *alt; - int ret; + int ret = 0; + + if (offset == UCSI_CCI) { + spin_lock(&uc->op_lock); + memcpy(val, &(uc->op_data).cci, val_len); + spin_unlock(&uc->op_lock); + } else if (offset == UCSI_MESSAGE_IN) { + spin_lock(&uc->op_lock); + memcpy(val, &(uc->op_data).message_in, val_len); + spin_unlock(&uc->op_lock); + } else { + ret = ccg_read(uc, reg, val, val_len); + } - ret = ccg_read(uc, reg, val, val_len); if (ret) return ret; @@ -559,9 +613,18 @@ static int ucsi_ccg_read(struct ucsi *ucsi, unsigned int offset, static int ucsi_ccg_async_write(struct ucsi *ucsi, unsigned int offset, const void *val, size_t val_len) { + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset); - return ccg_write(ucsi_get_drvdata(ucsi), reg, val, val_len); + /* + * UCSI may read CCI instantly after async_write, + * clear CCI to avoid caller getting wrong data before we get CCI from ISR + */ + spin_lock(&uc->op_lock); + uc->op_data.cci = 0; + spin_unlock(&uc->op_lock); + + return ccg_write(uc, reg, val, val_len); } static int ucsi_ccg_sync_write(struct ucsi *ucsi, unsigned int offset, @@ -615,13 +678,18 @@ static irqreturn_t ccg_irq_handler(int irq, void *data) u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CCI); struct ucsi_ccg *uc = data; u8 intr_reg; - u32 cci; - int ret; + u32 cci = 0; + int ret = 0; ret = ccg_read(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); if (ret) return ret; + if (!intr_reg) + return IRQ_HANDLED; + else if (!(intr_reg & UCSI_READ_INT)) + goto err_clear_irq; + ret = ccg_read(uc, reg, (void *)&cci, sizeof(cci)); if (ret) goto err_clear_irq; @@ -629,13 +697,21 @@ static irqreturn_t ccg_irq_handler(int irq, void *data) if (UCSI_CCI_CONNECTOR(cci)) ucsi_connector_change(uc->ucsi, UCSI_CCI_CONNECTOR(cci)); - if (test_bit(DEV_CMD_PENDING, &uc->flags) && - cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) - complete(&uc->complete); + /* + * As per CCGx UCSI interface guide, copy CCI and MESSAGE_IN + * to the OpRegion before clear the UCSI interrupt + */ + ret = ccg_op_region_update(uc, cci); + if (ret) + goto err_clear_irq; err_clear_irq: ccg_write(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); + if (!ret && test_bit(DEV_CMD_PENDING, &uc->flags) && + cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) + complete(&uc->complete); + return IRQ_HANDLED; } |