diff options
Diffstat (limited to 'drivers/usb/host/xhci-tegra.c')
| -rw-r--r-- | drivers/usb/host/xhci-tegra.c | 1882 |
1 files changed, 1642 insertions, 240 deletions
diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 938ff06c0349..31ccced5125e 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -2,7 +2,7 @@ /* * NVIDIA Tegra xHCI host controller driver * - * Copyright (C) 2014 NVIDIA Corporation + * Copyright (c) 2014-2020, NVIDIA CORPORATION. All rights reserved. * Copyright (C) 2014 Google, Inc. */ @@ -11,18 +11,25 @@ #include <linux/dma-mapping.h> #include <linux/firmware.h> #include <linux/interrupt.h> +#include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/of_irq.h> #include <linux/phy/phy.h> #include <linux/phy/tegra/xusb.h> #include <linux/platform_device.h> +#include <linux/usb/ch9.h> #include <linux/pm.h> #include <linux/pm_domain.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/reset.h> #include <linux/slab.h> +#include <linux/string_choices.h> +#include <linux/usb/otg.h> +#include <linux/usb/phy.h> +#include <linux/usb/role.h> #include <soc/tegra/pmc.h> #include "xhci.h" @@ -38,23 +45,33 @@ #define XUSB_CFG_4 0x010 #define XUSB_BASE_ADDR_SHIFT 15 #define XUSB_BASE_ADDR_MASK 0x1ffff +#define XUSB_CFG_7 0x01c +#define XUSB_BASE2_ADDR_SHIFT 16 +#define XUSB_BASE2_ADDR_MASK 0xffff +#define XUSB_CFG_16 0x040 +#define XUSB_CFG_24 0x060 +#define XUSB_CFG_AXI_CFG 0x0f8 #define XUSB_CFG_ARU_C11_CSBRANGE 0x41c +#define XUSB_CFG_ARU_CONTEXT 0x43c +#define XUSB_CFG_ARU_CONTEXT_HS_PLS 0x478 +#define XUSB_CFG_ARU_CONTEXT_FS_PLS 0x47c +#define XUSB_CFG_ARU_CONTEXT_HSFS_SPEED 0x480 +#define XUSB_CFG_ARU_CONTEXT_HSFS_PP 0x484 #define XUSB_CFG_CSB_BASE_ADDR 0x800 /* FPCI mailbox registers */ -#define XUSB_CFG_ARU_MBOX_CMD 0x0e4 +/* XUSB_CFG_ARU_MBOX_CMD */ #define MBOX_DEST_FALC BIT(27) #define MBOX_DEST_PME BIT(28) #define MBOX_DEST_SMI BIT(29) #define MBOX_DEST_XHCI BIT(30) #define MBOX_INT_EN BIT(31) -#define XUSB_CFG_ARU_MBOX_DATA_IN 0x0e8 +/* XUSB_CFG_ARU_MBOX_DATA_IN and XUSB_CFG_ARU_MBOX_DATA_OUT */ #define CMD_DATA_SHIFT 0 #define CMD_DATA_MASK 0xffffff #define CMD_TYPE_SHIFT 24 #define CMD_TYPE_MASK 0xff -#define XUSB_CFG_ARU_MBOX_DATA_OUT 0x0ec -#define XUSB_CFG_ARU_MBOX_OWNER 0x0f0 +/* XUSB_CFG_ARU_MBOX_OWNER */ #define MBOX_OWNER_NONE 0 #define MBOX_OWNER_FW 1 #define MBOX_OWNER_SW 2 @@ -62,12 +79,35 @@ #define MBOX_SMI_INTR_FW_HANG BIT(1) #define MBOX_SMI_INTR_EN BIT(3) +/* BAR2 registers */ +#define XUSB_BAR2_ARU_MBOX_CMD 0x004 +#define XUSB_BAR2_ARU_MBOX_DATA_IN 0x008 +#define XUSB_BAR2_ARU_MBOX_DATA_OUT 0x00c +#define XUSB_BAR2_ARU_MBOX_OWNER 0x010 +#define XUSB_BAR2_ARU_SMI_INTR 0x014 +#define XUSB_BAR2_ARU_SMI_ARU_FW_SCRATCH_DATA0 0x01c +#define XUSB_BAR2_ARU_IFRDMA_CFG0 0x0e0 +#define XUSB_BAR2_ARU_IFRDMA_CFG1 0x0e4 +#define XUSB_BAR2_ARU_IFRDMA_STREAMID_FIELD 0x0e8 +#define XUSB_BAR2_ARU_C11_CSBRANGE 0x9c +#define XUSB_BAR2_ARU_FW_SCRATCH 0x1000 +#define XUSB_BAR2_CSB_BASE_ADDR 0x2000 + /* IPFS registers */ +#define IPFS_XUSB_HOST_MSI_BAR_SZ_0 0x0c0 +#define IPFS_XUSB_HOST_MSI_AXI_BAR_ST_0 0x0c4 +#define IPFS_XUSB_HOST_MSI_FPCI_BAR_ST_0 0x0c8 +#define IPFS_XUSB_HOST_MSI_VEC0_0 0x100 +#define IPFS_XUSB_HOST_MSI_EN_VEC0_0 0x140 #define IPFS_XUSB_HOST_CONFIGURATION_0 0x180 #define IPFS_EN_FPCI BIT(0) +#define IPFS_XUSB_HOST_FPCI_ERROR_MASKS_0 0x184 #define IPFS_XUSB_HOST_INTR_MASK_0 0x188 #define IPFS_IP_INT_MASK BIT(16) +#define IPFS_XUSB_HOST_INTR_ENABLE_0 0x198 +#define IPFS_XUSB_HOST_UFPCI_CONFIG_0 0x19c #define IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0 0x1bc +#define IPFS_XUSB_HOST_MCCIF_FIFOCTRL_0 0x1dc #define CSB_PAGE_SELECT_MASK 0x7fffff #define CSB_PAGE_SELECT_SHIFT 9 @@ -89,6 +129,9 @@ #define IMFILLRNG1_TAG_HI_SHIFT 16 #define XUSB_FALC_IMFILLCTL 0x158 +/* CSB ARU registers */ +#define XUSB_CSB_ARU_SCRATCH0 0x100100 + /* MP CSB registers */ #define XUSB_CSB_MP_ILOAD_ATTR 0x101a00 #define XUSB_CSB_MP_ILOAD_BASE_LO 0x101a04 @@ -102,11 +145,18 @@ #define L2IMEMOP_ACTION_SHIFT 24 #define L2IMEMOP_INVALIDATE_ALL (0x40 << L2IMEMOP_ACTION_SHIFT) #define L2IMEMOP_LOAD_LOCKED_RESULT (0x11 << L2IMEMOP_ACTION_SHIFT) +#define XUSB_CSB_MEMPOOL_L2IMEMOP_RESULT 0x101a18 +#define L2IMEMOP_RESULT_VLD BIT(31) #define XUSB_CSB_MP_APMAP 0x10181c #define APMAP_BOOTPATH BIT(31) #define IMEM_BLOCK_SIZE 256 +#define FW_IOCTL_TYPE_SHIFT 24 +#define FW_IOCTL_CFGTBL_READ 17 + +#define WAKE_IRQ_START_INDEX 2 + struct tegra_xusb_fw_header { __le32 boot_loadaddr_in_imem; __le32 boot_codedfi_offset; @@ -146,12 +196,42 @@ struct tegra_xusb_phy_type { unsigned int num; }; +struct tegra_xusb_mbox_regs { + u16 cmd; + u16 data_in; + u16 data_out; + u16 owner; + u16 smi_intr; +}; + +struct tegra_xusb_context_soc { + struct { + const unsigned int *offsets; + unsigned int num_offsets; + } ipfs; + + struct { + const unsigned int *offsets; + unsigned int num_offsets; + } fpci; +}; + +struct tegra_xusb; +struct tegra_xusb_soc_ops { + u32 (*mbox_reg_readl)(struct tegra_xusb *tegra, unsigned int offset); + void (*mbox_reg_writel)(struct tegra_xusb *tegra, u32 value, unsigned int offset); + u32 (*csb_reg_readl)(struct tegra_xusb *tegra, unsigned int offset); + void (*csb_reg_writel)(struct tegra_xusb *tegra, u32 value, unsigned int offset); +}; + struct tegra_xusb_soc { const char *firmware; const char * const *supply_names; unsigned int num_supplies; const struct tegra_xusb_phy_type *phy_types; unsigned int num_types; + unsigned int max_num_wakes; + const struct tegra_xusb_context_soc *context; struct { struct { @@ -160,7 +240,20 @@ struct tegra_xusb_soc { } usb2, ulpi, hsic, usb3; } ports; + struct tegra_xusb_mbox_regs mbox; + const struct tegra_xusb_soc_ops *ops; + bool scale_ss_clock; + bool has_ipfs; + bool lpm_support; + bool otg_reset_sspi; + + bool has_bar2; +}; + +struct tegra_xusb_context { + u32 *ipfs; + u32 *fpci; }; struct tegra_xusb { @@ -172,9 +265,13 @@ struct tegra_xusb { int xhci_irq; int mbox_irq; + int padctl_irq; + int *wake_irqs; void __iomem *ipfs_base; void __iomem *fpci_base; + void __iomem *bar2_base; + struct resource *bar2; const struct tegra_xusb_soc *soc; @@ -197,18 +294,30 @@ struct tegra_xusb { struct device *genpd_dev_host; struct device *genpd_dev_ss; - struct device_link *genpd_dl_host; - struct device_link *genpd_dl_ss; + bool use_genpd; struct phy **phys; unsigned int num_phys; + struct usb_phy **usbphy; + unsigned int num_usb_phys; + int otg_usb2_port; + int otg_usb3_port; + bool host_mode; + struct notifier_block id_nb; + struct work_struct id_work; + /* Firmware loading related */ struct { size_t size; void *virt; dma_addr_t phys; } fw; + + bool suspended; + struct tegra_xusb_context context; + u8 lp0_utmi_pad_mask; + int num_wakes; }; static struct hc_driver __read_mostly tegra_xhci_hc_driver; @@ -235,8 +344,34 @@ static inline void ipfs_writel(struct tegra_xusb *tegra, u32 value, writel(value, tegra->ipfs_base + offset); } +static inline u32 bar2_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + return readl(tegra->bar2_base + offset); +} + +static inline void bar2_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + writel(value, tegra->bar2_base + offset); +} + static u32 csb_readl(struct tegra_xusb *tegra, unsigned int offset) { + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + + return ops->csb_reg_readl(tegra, offset); +} + +static void csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + + ops->csb_reg_writel(tegra, value, offset); +} + +static u32 fpci_csb_readl(struct tegra_xusb *tegra, unsigned int offset) +{ u32 page = CSB_PAGE_SELECT(offset); u32 ofs = CSB_PAGE_OFFSET(offset); @@ -245,8 +380,8 @@ static u32 csb_readl(struct tegra_xusb *tegra, unsigned int offset) return fpci_readl(tegra, XUSB_CFG_CSB_BASE_ADDR + ofs); } -static void csb_writel(struct tegra_xusb *tegra, u32 value, - unsigned int offset) +static void fpci_csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) { u32 page = CSB_PAGE_SELECT(offset); u32 ofs = CSB_PAGE_OFFSET(offset); @@ -255,6 +390,26 @@ static void csb_writel(struct tegra_xusb *tegra, u32 value, fpci_writel(tegra, value, XUSB_CFG_CSB_BASE_ADDR + ofs); } +static u32 bar2_csb_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + bar2_writel(tegra, page, XUSB_BAR2_ARU_C11_CSBRANGE); + + return bar2_readl(tegra, XUSB_BAR2_CSB_BASE_ADDR + ofs); +} + +static void bar2_csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + bar2_writel(tegra, page, XUSB_BAR2_ARU_C11_CSBRANGE); + bar2_writel(tegra, value, XUSB_BAR2_CSB_BASE_ADDR + ofs); +} + static int tegra_xusb_set_ss_clk(struct tegra_xusb *tegra, unsigned long rate) { @@ -353,29 +508,6 @@ enum tegra_xusb_mbox_cmd { MBOX_CMD_NAK }; -static const char * const mbox_cmd_name[] = { - [ 1] = "MSG_ENABLE", - [ 2] = "INC_FALCON_CLOCK", - [ 3] = "DEC_FALCON_CLOCK", - [ 4] = "INC_SSPI_CLOCK", - [ 5] = "DEC_SSPI_CLOCK", - [ 6] = "SET_BW", - [ 7] = "SET_SS_PWR_GATING", - [ 8] = "SET_SS_PWR_UNGATING", - [ 9] = "SAVE_DFE_CTLE_CTX", - [ 10] = "AIRPLANE_MODE_ENABLED", - [ 11] = "AIRPLANE_MODE_DISABLED", - [ 12] = "START_HSIC_IDLE", - [ 13] = "STOP_HSIC_IDLE", - [ 14] = "DBC_WAKE_STACK", - [ 15] = "HSIC_PRETEND_CONNECT", - [ 16] = "RESET_SSPI", - [ 17] = "DISABLE_SS_LFPS_DETECTION", - [ 18] = "ENABLE_SS_LFPS_DETECTION", - [128] = "ACK", - [129] = "NAK", -}; - struct tegra_xusb_mbox_msg { u32 cmd; u32 data; @@ -409,6 +541,7 @@ static bool tegra_xusb_mbox_cmd_requires_ack(enum tegra_xusb_mbox_cmd cmd) static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, const struct tegra_xusb_mbox_msg *msg) { + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; bool wait_for_idle = false; u32 value; @@ -417,15 +550,15 @@ static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, * ACK/NAK messages. */ if (!(msg->cmd == MBOX_CMD_ACK || msg->cmd == MBOX_CMD_NAK)) { - value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); if (value != MBOX_OWNER_NONE) { dev_err(tegra->dev, "mailbox is busy\n"); return -EBUSY; } - fpci_writel(tegra, MBOX_OWNER_SW, XUSB_CFG_ARU_MBOX_OWNER); + ops->mbox_reg_writel(tegra, MBOX_OWNER_SW, tegra->soc->mbox.owner); - value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); if (value != MBOX_OWNER_SW) { dev_err(tegra->dev, "failed to acquire mailbox\n"); return -EBUSY; @@ -435,17 +568,17 @@ static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, } value = tegra_xusb_mbox_pack(msg); - fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_DATA_IN); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.data_in); - value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_CMD); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.cmd); value |= MBOX_INT_EN | MBOX_DEST_FALC; - fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_CMD); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.cmd); if (wait_for_idle) { unsigned long timeout = jiffies + msecs_to_jiffies(250); while (time_before(jiffies, timeout)) { - value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); if (value == MBOX_OWNER_NONE) break; @@ -453,7 +586,7 @@ static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, } if (time_after(jiffies, timeout)) - value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); if (value != MBOX_OWNER_NONE) return -ETIMEDOUT; @@ -465,11 +598,12 @@ static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, static irqreturn_t tegra_xusb_mbox_irq(int irq, void *data) { struct tegra_xusb *tegra = data; + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; u32 value; /* clear mailbox interrupts */ - value = fpci_readl(tegra, XUSB_CFG_ARU_SMI_INTR); - fpci_writel(tegra, value, XUSB_CFG_ARU_SMI_INTR); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.smi_intr); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.smi_intr); if (value & MBOX_SMI_INTR_FW_HANG) dev_err(tegra->dev, "controller firmware hang\n"); @@ -584,12 +718,19 @@ static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra, enable); if (err < 0) break; + + /* + * wait 500us for LFPS detector to be disabled before + * sending ACK + */ + if (!enable) + usleep_range(500, 1000); } if (err < 0) { dev_err(dev, "failed to %s LFPS detection on USB3#%u: %d\n", - enable ? "enable" : "disable", port, err); + str_enable_disable(enable), port, err); rsp.cmd = MBOX_CMD_NAK; } else { rsp.cmd = MBOX_CMD_ACK; @@ -615,45 +756,61 @@ static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra, static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) { struct tegra_xusb *tegra = data; + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; struct tegra_xusb_mbox_msg msg; u32 value; mutex_lock(&tegra->lock); - value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_DATA_OUT); + if (pm_runtime_suspended(tegra->dev) || tegra->suspended) + goto out; + + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.data_out); tegra_xusb_mbox_unpack(&msg, value); - value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_CMD); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.cmd); value &= ~MBOX_DEST_SMI; - fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_CMD); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.cmd); /* clear mailbox owner if no ACK/NAK is required */ if (!tegra_xusb_mbox_cmd_requires_ack(msg.cmd)) - fpci_writel(tegra, MBOX_OWNER_NONE, XUSB_CFG_ARU_MBOX_OWNER); + ops->mbox_reg_writel(tegra, MBOX_OWNER_NONE, tegra->soc->mbox.owner); tegra_xusb_mbox_handle(tegra, &msg); +out: mutex_unlock(&tegra->lock); return IRQ_HANDLED; } -static void tegra_xusb_ipfs_config(struct tegra_xusb *tegra, - struct resource *regs) +static void tegra_xusb_config(struct tegra_xusb *tegra) { + u32 regs = tegra->hcd->rsrc_start; u32 value; - value = ipfs_readl(tegra, IPFS_XUSB_HOST_CONFIGURATION_0); - value |= IPFS_EN_FPCI; - ipfs_writel(tegra, value, IPFS_XUSB_HOST_CONFIGURATION_0); + if (tegra->soc->has_ipfs) { + value = ipfs_readl(tegra, IPFS_XUSB_HOST_CONFIGURATION_0); + value |= IPFS_EN_FPCI; + ipfs_writel(tegra, value, IPFS_XUSB_HOST_CONFIGURATION_0); - usleep_range(10, 20); + usleep_range(10, 20); + } /* Program BAR0 space */ value = fpci_readl(tegra, XUSB_CFG_4); value &= ~(XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); - value |= regs->start & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); + value |= regs & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); fpci_writel(tegra, value, XUSB_CFG_4); + /* Program BAR2 space */ + if (tegra->bar2) { + value = fpci_readl(tegra, XUSB_CFG_7); + value &= ~(XUSB_BASE2_ADDR_MASK << XUSB_BASE2_ADDR_SHIFT); + value |= tegra->bar2->start & + (XUSB_BASE2_ADDR_MASK << XUSB_BASE2_ADDR_SHIFT); + fpci_writel(tegra, value, XUSB_CFG_7); + } + usleep_range(100, 200); /* Enable bus master */ @@ -661,13 +818,15 @@ static void tegra_xusb_ipfs_config(struct tegra_xusb *tegra, value |= XUSB_IO_SPACE_EN | XUSB_MEM_SPACE_EN | XUSB_BUS_MASTER_EN; fpci_writel(tegra, value, XUSB_CFG_1); - /* Enable interrupt assertion */ - value = ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0); - value |= IPFS_IP_INT_MASK; - ipfs_writel(tegra, value, IPFS_XUSB_HOST_INTR_MASK_0); + if (tegra->soc->has_ipfs) { + /* Enable interrupt assertion */ + value = ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0); + value |= IPFS_IP_INT_MASK; + ipfs_writel(tegra, value, IPFS_XUSB_HOST_INTR_MASK_0); - /* Set hysteresis */ - ipfs_writel(tegra, 0x80, IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0); + /* Set hysteresis */ + ipfs_writel(tegra, 0x80, IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0); + } } static int tegra_xusb_clk_enable(struct tegra_xusb *tegra) @@ -769,60 +928,34 @@ static void tegra_xusb_phy_disable(struct tegra_xusb *tegra) } } -static int tegra_xusb_runtime_suspend(struct device *dev) +#ifdef CONFIG_PM_SLEEP +static int tegra_xusb_init_context(struct tegra_xusb *tegra) { - struct tegra_xusb *tegra = dev_get_drvdata(dev); + const struct tegra_xusb_context_soc *soc = tegra->soc->context; - tegra_xusb_phy_disable(tegra); - regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); - tegra_xusb_clk_disable(tegra); + tegra->context.ipfs = devm_kcalloc(tegra->dev, soc->ipfs.num_offsets, + sizeof(u32), GFP_KERNEL); + if (!tegra->context.ipfs) + return -ENOMEM; + + tegra->context.fpci = devm_kcalloc(tegra->dev, soc->fpci.num_offsets, + sizeof(u32), GFP_KERNEL); + if (!tegra->context.fpci) + return -ENOMEM; return 0; } - -static int tegra_xusb_runtime_resume(struct device *dev) +#else +static inline int tegra_xusb_init_context(struct tegra_xusb *tegra) { - struct tegra_xusb *tegra = dev_get_drvdata(dev); - int err; - - err = tegra_xusb_clk_enable(tegra); - if (err) { - dev_err(dev, "failed to enable clocks: %d\n", err); - return err; - } - - err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies); - if (err) { - dev_err(dev, "failed to enable regulators: %d\n", err); - goto disable_clk; - } - - err = tegra_xusb_phy_enable(tegra); - if (err < 0) { - dev_err(dev, "failed to enable PHYs: %d\n", err); - goto disable_regulator; - } - return 0; - -disable_regulator: - regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); -disable_clk: - tegra_xusb_clk_disable(tegra); - return err; } +#endif -static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) +static int tegra_xusb_request_firmware(struct tegra_xusb *tegra) { - unsigned int code_tag_blocks, code_size_blocks, code_blocks; struct tegra_xusb_fw_header *header; - struct device *dev = tegra->dev; const struct firmware *fw; - unsigned long timeout; - time64_t timestamp; - struct tm time; - u64 address; - u32 value; int err; err = request_firmware(&fw, tegra->soc->firmware, tegra->dev); @@ -847,6 +980,40 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) memcpy(tegra->fw.virt, fw->data, tegra->fw.size); release_firmware(fw); + return 0; +} + +static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) +{ + struct xhci_cap_regs __iomem *cap_regs; + struct xhci_op_regs __iomem *op_regs; + int ret; + u32 value; + + cap_regs = tegra->regs; + op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase)); + + ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); + + if (ret) + dev_err(tegra->dev, "XHCI Controller not ready. Falcon state: 0x%x\n", + csb_readl(tegra, XUSB_FALC_CPUCTL)); + + return ret; +} + +static int tegra_xusb_load_firmware_rom(struct tegra_xusb *tegra) +{ + unsigned int code_tag_blocks, code_size_blocks, code_blocks; + struct tegra_xusb_fw_header *header; + struct device *dev = tegra->dev; + time64_t timestamp; + u64 address; + u32 value; + int err; + + header = (struct tegra_xusb_fw_header *)tegra->fw.virt; + if (csb_readl(tegra, XUSB_CSB_MP_ILOAD_BASE_LO) != 0) { dev_info(dev, "Firmware already loaded, Falcon state %#x\n", csb_readl(tegra, XUSB_FALC_CPUCTL)); @@ -901,49 +1068,80 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) csb_writel(tegra, 0, XUSB_FALC_DMACTL); - msleep(50); + /* wait for RESULT_VLD to get set */ +#define tegra_csb_readl(offset) csb_readl(tegra, offset) + err = readx_poll_timeout(tegra_csb_readl, + XUSB_CSB_MEMPOOL_L2IMEMOP_RESULT, value, + value & L2IMEMOP_RESULT_VLD, 100, 10000); + if (err < 0) { + dev_err(dev, "DMA controller not ready %#010x\n", value); + return err; + } +#undef tegra_csb_readl csb_writel(tegra, le32_to_cpu(header->boot_codetag), XUSB_FALC_BOOTVEC); - /* Boot Falcon CPU and wait for it to enter the STOPPED (idle) state. */ - timeout = jiffies + msecs_to_jiffies(5); - + /* Boot Falcon CPU and wait for USBSTS_CNR to get cleared. */ csb_writel(tegra, CPUCTL_STARTCPU, XUSB_FALC_CPUCTL); - while (time_before(jiffies, timeout)) { - if (csb_readl(tegra, XUSB_FALC_CPUCTL) == CPUCTL_STATE_STOPPED) - break; + if (tegra_xusb_wait_for_falcon(tegra)) + return -EIO; - usleep_range(100, 200); - } + timestamp = le32_to_cpu(header->fwimg_created_time); - if (csb_readl(tegra, XUSB_FALC_CPUCTL) != CPUCTL_STATE_STOPPED) { - dev_err(dev, "Falcon failed to start, state: %#x\n", - csb_readl(tegra, XUSB_FALC_CPUCTL)); + dev_info(dev, "Firmware timestamp: %ptTs UTC\n", ×tamp); + + return 0; +} + +static u32 tegra_xusb_read_firmware_header(struct tegra_xusb *tegra, u32 offset) +{ + /* + * We only accept reading the firmware config table + * The offset should not exceed the fw header structure + */ + if (offset >= sizeof(struct tegra_xusb_fw_header)) + return 0; + + bar2_writel(tegra, (FW_IOCTL_CFGTBL_READ << FW_IOCTL_TYPE_SHIFT) | offset, + XUSB_BAR2_ARU_FW_SCRATCH); + return bar2_readl(tegra, XUSB_BAR2_ARU_SMI_ARU_FW_SCRATCH_DATA0); +} + +static int tegra_xusb_init_ifr_firmware(struct tegra_xusb *tegra) +{ + time64_t timestamp; + + if (tegra_xusb_wait_for_falcon(tegra)) return -EIO; - } - timestamp = le32_to_cpu(header->fwimg_created_time); - time64_to_tm(timestamp, 0, &time); +#define offsetof_32(X, Y) ((u8)(offsetof(X, Y) / sizeof(__le32))) + timestamp = tegra_xusb_read_firmware_header(tegra, offsetof_32(struct tegra_xusb_fw_header, + fwimg_created_time) << 2); - dev_info(dev, "Firmware timestamp: %ld-%02d-%02d %02d:%02d:%02d UTC\n", - time.tm_year + 1900, time.tm_mon + 1, time.tm_mday, - time.tm_hour, time.tm_min, time.tm_sec); + dev_info(tegra->dev, "Firmware timestamp: %ptTs UTC\n", ×tamp); return 0; } +static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) +{ + if (!tegra->soc->firmware) + return tegra_xusb_init_ifr_firmware(tegra); + else + return tegra_xusb_load_firmware_rom(tegra); +} + static void tegra_xusb_powerdomain_remove(struct device *dev, struct tegra_xusb *tegra) { - if (tegra->genpd_dl_ss) - device_link_del(tegra->genpd_dl_ss); - if (tegra->genpd_dl_host) - device_link_del(tegra->genpd_dl_host); - if (tegra->genpd_dev_ss) + if (!tegra->use_genpd) + return; + + if (!IS_ERR_OR_NULL(tegra->genpd_dev_ss)) dev_pm_domain_detach(tegra->genpd_dev_ss, true); - if (tegra->genpd_dev_host) + if (!IS_ERR_OR_NULL(tegra->genpd_dev_host)) dev_pm_domain_detach(tegra->genpd_dev_host, true); } @@ -966,30 +1164,440 @@ static int tegra_xusb_powerdomain_init(struct device *dev, return err; } - tegra->genpd_dl_host = device_link_add(dev, tegra->genpd_dev_host, - DL_FLAG_PM_RUNTIME | - DL_FLAG_STATELESS); - if (!tegra->genpd_dl_host) { - dev_err(dev, "adding host device link failed!\n"); - return -ENODEV; + tegra->use_genpd = true; + + return 0; +} + +static int tegra_xusb_unpowergate_partitions(struct tegra_xusb *tegra) +{ + struct device *dev = tegra->dev; + int rc; + + if (tegra->use_genpd) { + rc = pm_runtime_resume_and_get(tegra->genpd_dev_ss); + if (rc < 0) { + dev_err(dev, "failed to enable XUSB SS partition\n"); + return rc; + } + + rc = pm_runtime_resume_and_get(tegra->genpd_dev_host); + if (rc < 0) { + dev_err(dev, "failed to enable XUSB Host partition\n"); + pm_runtime_put_sync(tegra->genpd_dev_ss); + return rc; + } + } else { + rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA, + tegra->ss_clk, + tegra->ss_rst); + if (rc < 0) { + dev_err(dev, "failed to enable XUSB SS partition\n"); + return rc; + } + + rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, + tegra->host_clk, + tegra->host_rst); + if (rc < 0) { + dev_err(dev, "failed to enable XUSB Host partition\n"); + tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + return rc; + } } - tegra->genpd_dl_ss = device_link_add(dev, tegra->genpd_dev_ss, - DL_FLAG_PM_RUNTIME | - DL_FLAG_STATELESS); - if (!tegra->genpd_dl_ss) { - dev_err(dev, "adding superspeed device link failed!\n"); - return -ENODEV; + return 0; +} + +static int tegra_xusb_powergate_partitions(struct tegra_xusb *tegra) +{ + struct device *dev = tegra->dev; + int rc; + + if (tegra->use_genpd) { + rc = pm_runtime_put_sync(tegra->genpd_dev_host); + if (rc < 0) { + dev_err(dev, "failed to disable XUSB Host partition\n"); + return rc; + } + + rc = pm_runtime_put_sync(tegra->genpd_dev_ss); + if (rc < 0) { + dev_err(dev, "failed to disable XUSB SS partition\n"); + pm_runtime_get_sync(tegra->genpd_dev_host); + return rc; + } + } else { + rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); + if (rc < 0) { + dev_err(dev, "failed to disable XUSB Host partition\n"); + return rc; + } + + rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + if (rc < 0) { + dev_err(dev, "failed to disable XUSB SS partition\n"); + tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, + tegra->host_clk, + tegra->host_rst); + return rc; + } } return 0; } -static int tegra_xusb_probe(struct platform_device *pdev) +static int __tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) { struct tegra_xusb_mbox_msg msg; - struct resource *res, *regs; + int err; + + /* Enable firmware messages from controller. */ + msg.cmd = MBOX_CMD_MSG_ENABLED; + msg.data = 0; + + err = tegra_xusb_mbox_send(tegra, &msg); + if (err < 0) + dev_err(tegra->dev, "failed to enable messages: %d\n", err); + + return err; +} + +static irqreturn_t tegra_xusb_padctl_irq(int irq, void *data) +{ + struct tegra_xusb *tegra = data; + + mutex_lock(&tegra->lock); + + if (tegra->suspended) { + mutex_unlock(&tegra->lock); + return IRQ_HANDLED; + } + + mutex_unlock(&tegra->lock); + + pm_runtime_resume(tegra->dev); + + return IRQ_HANDLED; +} + +static int tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) +{ + int err; + + mutex_lock(&tegra->lock); + err = __tegra_xusb_enable_firmware_messages(tegra); + mutex_unlock(&tegra->lock); + + return err; +} + +static void tegra_xhci_set_port_power(struct tegra_xusb *tegra, bool main, + bool set) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct usb_hcd *hcd = main ? xhci->main_hcd : xhci->shared_hcd; + unsigned int wait = (!main && !set) ? 1000 : 10; + u16 typeReq = set ? SetPortFeature : ClearPortFeature; + u16 wIndex = main ? tegra->otg_usb2_port + 1 : tegra->otg_usb3_port + 1; + u32 status; + u32 stat_power = main ? USB_PORT_STAT_POWER : USB_SS_PORT_STAT_POWER; + u32 status_val = set ? stat_power : 0; + + dev_dbg(tegra->dev, "%s():%s %s port power\n", __func__, + set ? "set" : "clear", main ? "HS" : "SS"); + + hcd->driver->hub_control(hcd, typeReq, USB_PORT_FEAT_POWER, wIndex, + NULL, 0); + + do { + tegra_xhci_hc_driver.hub_control(hcd, GetPortStatus, 0, wIndex, + (char *) &status, sizeof(status)); + if (status_val == (status & stat_power)) + break; + + if (!main && !set) + usleep_range(600, 700); + else + usleep_range(10, 20); + } while (--wait > 0); + + if (status_val != (status & stat_power)) + dev_info(tegra->dev, "failed to %s %s PP %d\n", + set ? "set" : "clear", + main ? "HS" : "SS", status); +} + +static struct phy *tegra_xusb_get_phy(struct tegra_xusb *tegra, char *name, + int port) +{ + unsigned int i, phy_count = 0; + + for (i = 0; i < tegra->soc->num_types; i++) { + if (!strncmp(tegra->soc->phy_types[i].name, name, + strlen(name))) + return tegra->phys[phy_count+port]; + + phy_count += tegra->soc->phy_types[i].num; + } + + return NULL; +} + +static void tegra_xhci_id_work(struct work_struct *work) +{ + struct tegra_xusb *tegra = container_of(work, struct tegra_xusb, + id_work); + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct tegra_xusb_mbox_msg msg; + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", + tegra->otg_usb2_port); + u32 status; + int ret; + + dev_dbg(tegra->dev, "host mode %s\n", str_on_off(tegra->host_mode)); + + mutex_lock(&tegra->lock); + + if (tegra->host_mode) + phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_HOST); + else + phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); + + mutex_unlock(&tegra->lock); + + tegra->otg_usb3_port = tegra_xusb_padctl_get_usb3_companion(tegra->padctl, + tegra->otg_usb2_port); + + pm_runtime_get_sync(tegra->dev); + if (tegra->host_mode) { + /* switch to host mode */ + if (tegra->otg_usb3_port >= 0) { + if (tegra->soc->otg_reset_sspi) { + /* set PP=0 */ + tegra_xhci_hc_driver.hub_control( + xhci->shared_hcd, GetPortStatus, + 0, tegra->otg_usb3_port+1, + (char *) &status, sizeof(status)); + if (status & USB_SS_PORT_STAT_POWER) + tegra_xhci_set_port_power(tegra, false, + false); + + /* reset OTG port SSPI */ + msg.cmd = MBOX_CMD_RESET_SSPI; + msg.data = tegra->otg_usb3_port+1; + + ret = tegra_xusb_mbox_send(tegra, &msg); + if (ret < 0) { + dev_info(tegra->dev, + "failed to RESET_SSPI %d\n", + ret); + } + } + + tegra_xhci_set_port_power(tegra, false, true); + } + + tegra_xhci_set_port_power(tegra, true, true); + + } else { + if (tegra->otg_usb3_port >= 0) + tegra_xhci_set_port_power(tegra, false, false); + + tegra_xhci_set_port_power(tegra, true, false); + } + pm_runtime_put_autosuspend(tegra->dev); +} + +#if IS_ENABLED(CONFIG_PM) || IS_ENABLED(CONFIG_PM_SLEEP) +static bool is_usb2_otg_phy(struct tegra_xusb *tegra, unsigned int index) +{ + return (tegra->usbphy[index] != NULL); +} + +static bool is_usb3_otg_phy(struct tegra_xusb *tegra, unsigned int index) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + unsigned int i; + int port; + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (is_usb2_otg_phy(tegra, i)) { + port = tegra_xusb_padctl_get_usb3_companion(padctl, i); + if ((port >= 0) && (index == (unsigned int)port)) + return true; + } + } + + return false; +} + +static bool is_host_mode_phy(struct tegra_xusb *tegra, unsigned int phy_type, unsigned int index) +{ + if (strcmp(tegra->soc->phy_types[phy_type].name, "hsic") == 0) + return true; + + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb2") == 0) { + if (is_usb2_otg_phy(tegra, index)) + return ((index == tegra->otg_usb2_port) && tegra->host_mode); + else + return true; + } + + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb3") == 0) { + if (is_usb3_otg_phy(tegra, index)) + return ((index == tegra->otg_usb3_port) && tegra->host_mode); + else + return true; + } + + return false; +} +#endif + +static int tegra_xusb_get_usb2_port(struct tegra_xusb *tegra, + struct usb_phy *usbphy) +{ + unsigned int i; + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (tegra->usbphy[i] && usbphy == tegra->usbphy[i]) + return i; + } + + return -1; +} + +static int tegra_xhci_id_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct tegra_xusb *tegra = container_of(nb, struct tegra_xusb, + id_nb); + struct usb_phy *usbphy = (struct usb_phy *)data; + + dev_dbg(tegra->dev, "%s(): action is %d", __func__, usbphy->last_event); + + if ((tegra->host_mode && usbphy->last_event == USB_EVENT_ID) || + (!tegra->host_mode && usbphy->last_event != USB_EVENT_ID)) { + dev_dbg(tegra->dev, "Same role(%d) received. Ignore", + tegra->host_mode); + return NOTIFY_OK; + } + + tegra->otg_usb2_port = tegra_xusb_get_usb2_port(tegra, usbphy); + + tegra->host_mode = usbphy->last_event == USB_EVENT_ID; + + schedule_work(&tegra->id_work); + + return NOTIFY_OK; +} + +static int tegra_xusb_init_usb_phy(struct tegra_xusb *tegra) +{ + unsigned int i; + + tegra->usbphy = devm_kcalloc(tegra->dev, tegra->num_usb_phys, + sizeof(*tegra->usbphy), GFP_KERNEL); + if (!tegra->usbphy) + return -ENOMEM; + + INIT_WORK(&tegra->id_work, tegra_xhci_id_work); + tegra->id_nb.notifier_call = tegra_xhci_id_notify; + tegra->otg_usb2_port = -EINVAL; + tegra->otg_usb3_port = -EINVAL; + + for (i = 0; i < tegra->num_usb_phys; i++) { + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", i); + + if (!phy) + continue; + + tegra->usbphy[i] = devm_usb_get_phy_by_node(tegra->dev, + phy->dev.of_node, + &tegra->id_nb); + if (!IS_ERR(tegra->usbphy[i])) { + dev_dbg(tegra->dev, "usbphy-%d registered", i); + otg_set_host(tegra->usbphy[i]->otg, &tegra->hcd->self); + } else { + /* + * usb-phy is optional, continue if its not available. + */ + tegra->usbphy[i] = NULL; + } + } + + return 0; +} + +static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) +{ + unsigned int i; + + cancel_work_sync(&tegra->id_work); + + for (i = 0; i < tegra->num_usb_phys; i++) + if (tegra->usbphy[i]) + otg_set_host(tegra->usbphy[i]->otg, NULL); +} + +static int tegra_xusb_setup_wakeup(struct platform_device *pdev, struct tegra_xusb *tegra) +{ + unsigned int i; + + if (tegra->soc->max_num_wakes == 0) + return 0; + + tegra->wake_irqs = devm_kcalloc(tegra->dev, + tegra->soc->max_num_wakes, + sizeof(*tegra->wake_irqs), GFP_KERNEL); + if (!tegra->wake_irqs) + return -ENOMEM; + + /* + * USB wake events are independent of each other, so it is not necessary for a platform + * to utilize all wake-up events supported for a given device. The USB host can operate + * even if wake-up events are not defined or fail to be configured. Therefore, we only + * return critical errors, such as -ENOMEM. + */ + for (i = 0; i < tegra->soc->max_num_wakes; i++) { + struct irq_data *data; + + tegra->wake_irqs[i] = platform_get_irq(pdev, i + WAKE_IRQ_START_INDEX); + if (tegra->wake_irqs[i] < 0) + break; + + data = irq_get_irq_data(tegra->wake_irqs[i]); + if (!data) { + dev_warn(tegra->dev, "get wake event %d irq data fail\n", i); + irq_dispose_mapping(tegra->wake_irqs[i]); + break; + } + + irq_set_irq_type(tegra->wake_irqs[i], irqd_get_trigger_type(data)); + } + + tegra->num_wakes = i; + dev_dbg(tegra->dev, "setup %d wake events\n", tegra->num_wakes); + + return 0; +} + +static void tegra_xusb_dispose_wake(struct tegra_xusb *tegra) +{ + unsigned int i; + + for (i = 0; i < tegra->num_wakes; i++) + irq_dispose_mapping(tegra->wake_irqs[i]); + + tegra->num_wakes = 0; +} + +static int tegra_xusb_probe(struct platform_device *pdev) +{ struct tegra_xusb *tegra; + struct device_node *np; + struct resource *regs; struct xhci_hcd *xhci; unsigned int i, j, k; struct phy *phy; @@ -1005,20 +1613,27 @@ static int tegra_xusb_probe(struct platform_device *pdev) mutex_init(&tegra->lock); tegra->dev = &pdev->dev; - regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - tegra->regs = devm_ioremap_resource(&pdev->dev, regs); + err = tegra_xusb_init_context(tegra); + if (err < 0) + return err; + + tegra->regs = devm_platform_get_and_ioremap_resource(pdev, 0, ®s); if (IS_ERR(tegra->regs)) return PTR_ERR(tegra->regs); - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - tegra->fpci_base = devm_ioremap_resource(&pdev->dev, res); + tegra->fpci_base = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(tegra->fpci_base)) return PTR_ERR(tegra->fpci_base); - res = platform_get_resource(pdev, IORESOURCE_MEM, 2); - tegra->ipfs_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(tegra->ipfs_base)) - return PTR_ERR(tegra->ipfs_base); + if (tegra->soc->has_ipfs) { + tegra->ipfs_base = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(tegra->ipfs_base)) + return PTR_ERR(tegra->ipfs_base); + } else if (tegra->soc->has_bar2) { + tegra->bar2_base = devm_platform_get_and_ioremap_resource(pdev, 2, &tegra->bar2); + if (IS_ERR(tegra->bar2_base)) + return PTR_ERR(tegra->bar2_base); + } tegra->xhci_irq = platform_get_irq(pdev, 0); if (tegra->xhci_irq < 0) @@ -1028,9 +1643,32 @@ static int tegra_xusb_probe(struct platform_device *pdev) if (tegra->mbox_irq < 0) return tegra->mbox_irq; + err = tegra_xusb_setup_wakeup(pdev, tegra); + if (err) + return err; + tegra->padctl = tegra_xusb_padctl_get(&pdev->dev); - if (IS_ERR(tegra->padctl)) - return PTR_ERR(tegra->padctl); + if (IS_ERR(tegra->padctl)) { + err = PTR_ERR(tegra->padctl); + goto dispose_wake; + } + + np = of_parse_phandle(pdev->dev.of_node, "nvidia,xusb-padctl", 0); + if (!np) { + err = -ENODEV; + goto put_padctl; + } + + tegra->padctl_irq = of_irq_get(np, 0); + if (tegra->padctl_irq == -EPROBE_DEFER) { + err = tegra->padctl_irq; + goto put_padctl; + } else if (tegra->padctl_irq <= 0) { + /* Older device-trees don't have padctrl interrupt */ + tegra->padctl_irq = 0; + dev_dbg(&pdev->dev, + "%pOF is missing an interrupt, disabling PM support\n", np); + } tegra->host_clk = devm_clk_get(&pdev->dev, "xusb_host"); if (IS_ERR(tegra->host_clk)) { @@ -1095,7 +1733,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_padctl; } - if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) { + if (!of_property_present(pdev->dev.of_node, "power-domains")) { tegra->host_rst = devm_reset_control_get(&pdev->dev, "xusb_host"); if (IS_ERR(tegra->host_rst)) { @@ -1112,25 +1750,6 @@ static int tegra_xusb_probe(struct platform_device *pdev) err); goto put_padctl; } - - err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA, - tegra->ss_clk, - tegra->ss_rst); - if (err) { - dev_err(&pdev->dev, - "failed to enable XUSBA domain: %d\n", err); - goto put_padctl; - } - - err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, - tegra->host_clk, - tegra->host_rst); - if (err) { - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); - dev_err(&pdev->dev, - "failed to enable XUSBC domain: %d\n", err); - goto put_padctl; - } } else { err = tegra_xusb_powerdomain_init(&pdev->dev, tegra); if (err) @@ -1144,8 +1763,9 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_powerdomains; } - for (i = 0; i < tegra->soc->num_supplies; i++) - tegra->supplies[i].supply = tegra->soc->supply_names[i]; + regulator_bulk_set_supply_names(tegra->supplies, + tegra->soc->supply_names, + tegra->soc->num_supplies); err = devm_regulator_bulk_get(&pdev->dev, tegra->soc->num_supplies, tegra->supplies); @@ -1154,8 +1774,11 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_powerdomains; } - for (i = 0; i < tegra->soc->num_types; i++) + for (i = 0; i < tegra->soc->num_types; i++) { + if (!strncmp(tegra->soc->phy_types[i].name, "usb2", 4)) + tegra->num_usb_phys = tegra->soc->phy_types[i].num; tegra->num_phys += tegra->soc->phy_types[i].num; + } tegra->phys = devm_kcalloc(&pdev->dev, tegra->num_phys, sizeof(*tegra->phys), GFP_KERNEL); @@ -1191,39 +1814,70 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_powerdomains; } + tegra->hcd->skip_phy_initialization = 1; + tegra->hcd->regs = tegra->regs; + tegra->hcd->rsrc_start = regs->start; + tegra->hcd->rsrc_len = resource_size(regs); + /* * This must happen after usb_create_hcd(), because usb_create_hcd() * will overwrite the drvdata of the device with the hcd it creates. */ platform_set_drvdata(pdev, tegra); - pm_runtime_enable(&pdev->dev); - if (pm_runtime_enabled(&pdev->dev)) - err = pm_runtime_get_sync(&pdev->dev); - else - err = tegra_xusb_runtime_resume(&pdev->dev); + err = tegra_xusb_clk_enable(tegra); + if (err) { + dev_err(tegra->dev, "failed to enable clocks: %d\n", err); + goto put_hcd; + } + err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies); + if (err) { + dev_err(tegra->dev, "failed to enable regulators: %d\n", err); + goto disable_clk; + } + + err = tegra_xusb_phy_enable(tegra); if (err < 0) { - dev_err(&pdev->dev, "failed to enable device: %d\n", err); - goto disable_rpm; + dev_err(&pdev->dev, "failed to enable PHYs: %d\n", err); + goto disable_regulator; } - tegra_xusb_ipfs_config(tegra, regs); + /* + * The XUSB Falcon microcontroller can only address 40 bits, so set + * the DMA mask accordingly. + */ + err = dma_set_mask_and_coherent(tegra->dev, DMA_BIT_MASK(40)); + if (err < 0) { + dev_err(&pdev->dev, "failed to set DMA mask: %d\n", err); + goto disable_phy; + } + + if (tegra->soc->firmware) { + err = tegra_xusb_request_firmware(tegra); + if (err < 0) { + dev_err(&pdev->dev, + "failed to request firmware: %d\n", err); + goto disable_phy; + } + } + + err = tegra_xusb_unpowergate_partitions(tegra); + if (err) + goto free_firmware; + + tegra_xusb_config(tegra); err = tegra_xusb_load_firmware(tegra); if (err < 0) { dev_err(&pdev->dev, "failed to load firmware: %d\n", err); - goto put_rpm; + goto powergate; } - tegra->hcd->regs = tegra->regs; - tegra->hcd->rsrc_start = regs->start; - tegra->hcd->rsrc_len = resource_size(regs); - err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED); if (err < 0) { dev_err(&pdev->dev, "failed to add USB HCD: %d\n", err); - goto put_rpm; + goto powergate; } device_wakeup_enable(tegra->hcd->self.controller); @@ -1240,36 +1894,61 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto remove_usb2; } + if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); if (err < 0) { dev_err(&pdev->dev, "failed to add shared HCD: %d\n", err); goto put_usb3; } - mutex_lock(&tegra->lock); + err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq, + tegra_xusb_mbox_irq, + tegra_xusb_mbox_thread, 0, + dev_name(&pdev->dev), tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + goto remove_usb3; + } - /* Enable firmware messages from controller. */ - msg.cmd = MBOX_CMD_MSG_ENABLED; - msg.data = 0; + if (tegra->padctl_irq) { + err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, + NULL, tegra_xusb_padctl_irq, + IRQF_ONESHOT, dev_name(&pdev->dev), + tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err); + goto remove_usb3; + } + } - err = tegra_xusb_mbox_send(tegra, &msg); + err = tegra_xusb_enable_firmware_messages(tegra); if (err < 0) { dev_err(&pdev->dev, "failed to enable messages: %d\n", err); - mutex_unlock(&tegra->lock); goto remove_usb3; } - mutex_unlock(&tegra->lock); - - err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq, - tegra_xusb_mbox_irq, - tegra_xusb_mbox_thread, 0, - dev_name(&pdev->dev), tegra); + err = tegra_xusb_init_usb_phy(tegra); if (err < 0) { - dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + dev_err(&pdev->dev, "failed to init USB PHY: %d\n", err); goto remove_usb3; } + /* Enable wake for both USB 2.0 and USB 3.0 roothubs */ + device_init_wakeup(&tegra->hcd->self.root_hub->dev, true); + device_init_wakeup(&xhci->shared_hcd->self.root_hub->dev, true); + + pm_runtime_use_autosuspend(tegra->dev); + pm_runtime_set_autosuspend_delay(tegra->dev, 2000); + pm_runtime_mark_last_busy(tegra->dev); + pm_runtime_set_active(tegra->dev); + + if (tegra->padctl_irq) { + device_init_wakeup(tegra->dev, true); + pm_runtime_enable(tegra->dev); + } + return 0; remove_usb3: @@ -1278,29 +1957,46 @@ put_usb3: usb_put_hcd(xhci->shared_hcd); remove_usb2: usb_remove_hcd(tegra->hcd); -put_rpm: - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra_xusb_runtime_suspend(&pdev->dev); -disable_rpm: - pm_runtime_disable(&pdev->dev); +powergate: + tegra_xusb_powergate_partitions(tegra); +free_firmware: + dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt, + tegra->fw.phys); +disable_phy: + tegra_xusb_phy_disable(tegra); +disable_regulator: + regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); +disable_clk: + tegra_xusb_clk_disable(tegra); +put_hcd: usb_put_hcd(tegra->hcd); put_powerdomains: - if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) { - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); - } else { - tegra_xusb_powerdomain_remove(&pdev->dev, tegra); - } + tegra_xusb_powerdomain_remove(&pdev->dev, tegra); put_padctl: + of_node_put(np); tegra_xusb_padctl_put(tegra->padctl); +dispose_wake: + tegra_xusb_dispose_wake(tegra); return err; } -static int tegra_xusb_remove(struct platform_device *pdev) +static void tegra_xusb_disable(struct tegra_xusb *tegra) +{ + tegra_xusb_powergate_partitions(tegra); + tegra_xusb_powerdomain_remove(tegra->dev, tegra); + tegra_xusb_phy_disable(tegra); + tegra_xusb_clk_disable(tegra); + regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); +} + +static void tegra_xusb_remove(struct platform_device *pdev) { struct tegra_xusb *tegra = platform_get_drvdata(pdev); struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + tegra_xusb_deinit_usb_phy(tegra); + + pm_runtime_get_sync(&pdev->dev); usb_remove_hcd(xhci->shared_hcd); usb_put_hcd(xhci->shared_hcd); xhci->shared_hcd = NULL; @@ -1310,40 +2006,496 @@ static int tegra_xusb_remove(struct platform_device *pdev) dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt, tegra->fw.phys); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); + if (tegra->padctl_irq) + pm_runtime_disable(&pdev->dev); - if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) { - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); - } else { - tegra_xusb_powerdomain_remove(&pdev->dev, tegra); - } + tegra_xusb_dispose_wake(tegra); + + pm_runtime_put(&pdev->dev); + tegra_xusb_disable(tegra); tegra_xusb_padctl_put(tegra->padctl); +} + +static void tegra_xusb_shutdown(struct platform_device *pdev) +{ + struct tegra_xusb *tegra = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + disable_irq(tegra->xhci_irq); + xhci_shutdown(tegra->hcd); + tegra_xusb_disable(tegra); +} + +static bool xhci_hub_ports_suspended(struct xhci_hub *hub) +{ + struct device *dev = hub->hcd->self.controller; + bool status = true; + unsigned int i; + u32 value; + + for (i = 0; i < hub->num_ports; i++) { + value = xhci_portsc_readl(hub->ports[i]); + if ((value & PORT_PE) == 0) + continue; + + if ((value & PORT_PLS_MASK) != XDEV_U3) { + dev_info(dev, "%u-%u isn't suspended: %#010x\n", + hub->hcd->self.busnum, i + 1, value); + status = false; + } + } + + return status; +} + +static int tegra_xusb_check_ports(struct tegra_xusb *tegra) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct xhci_bus_state *bus_state = &xhci->usb2_rhub.bus_state; + unsigned long flags; + int err = 0; + + if (bus_state->bus_suspended) { + /* xusb_hub_suspend() has just directed one or more USB2 port(s) + * to U3 state, it takes 3ms to enter U3. + */ + usleep_range(3000, 4000); + } + + spin_lock_irqsave(&xhci->lock, flags); + + if (!xhci_hub_ports_suspended(&xhci->usb2_rhub) || + !xhci_hub_ports_suspended(&xhci->usb3_rhub)) + err = -EBUSY; + + spin_unlock_irqrestore(&xhci->lock, flags); + + return err; +} + +static void tegra_xusb_save_context(struct tegra_xusb *tegra) +{ + const struct tegra_xusb_context_soc *soc = tegra->soc->context; + struct tegra_xusb_context *ctx = &tegra->context; + unsigned int i; + + if (soc->ipfs.num_offsets > 0) { + for (i = 0; i < soc->ipfs.num_offsets; i++) + ctx->ipfs[i] = ipfs_readl(tegra, soc->ipfs.offsets[i]); + } + + if (soc->fpci.num_offsets > 0) { + for (i = 0; i < soc->fpci.num_offsets; i++) + ctx->fpci[i] = fpci_readl(tegra, soc->fpci.offsets[i]); + } +} + +static void tegra_xusb_restore_context(struct tegra_xusb *tegra) +{ + const struct tegra_xusb_context_soc *soc = tegra->soc->context; + struct tegra_xusb_context *ctx = &tegra->context; + unsigned int i; + + if (soc->fpci.num_offsets > 0) { + for (i = 0; i < soc->fpci.num_offsets; i++) + fpci_writel(tegra, ctx->fpci[i], soc->fpci.offsets[i]); + } + + if (soc->ipfs.num_offsets > 0) { + for (i = 0; i < soc->ipfs.num_offsets; i++) + ipfs_writel(tegra, ctx->ipfs[i], soc->ipfs.offsets[i]); + } +} + +static enum usb_device_speed tegra_xhci_portsc_to_speed(struct tegra_xusb *tegra, u32 portsc) +{ + if (DEV_LOWSPEED(portsc)) + return USB_SPEED_LOW; + + if (DEV_HIGHSPEED(portsc)) + return USB_SPEED_HIGH; + + if (DEV_FULLSPEED(portsc)) + return USB_SPEED_FULL; + + if (DEV_SUPERSPEED_ANY(portsc)) + return USB_SPEED_SUPER; + + return USB_SPEED_UNKNOWN; +} + +static void tegra_xhci_enable_phy_sleepwalk_wake(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + enum usb_device_speed speed; + struct phy *phy; + unsigned int index, offset; + unsigned int i, j, k; + struct xhci_hub *rhub; + u32 portsc; + + for (i = 0, k = 0; i < tegra->soc->num_types; i++) { + if (strcmp(tegra->soc->phy_types[i].name, "usb3") == 0) + rhub = &xhci->usb3_rhub; + else + rhub = &xhci->usb2_rhub; + + if (strcmp(tegra->soc->phy_types[i].name, "hsic") == 0) + offset = tegra->soc->ports.usb2.count; + else + offset = 0; + + for (j = 0; j < tegra->soc->phy_types[i].num; j++) { + phy = tegra->phys[k++]; + + if (!phy) + continue; + + index = j + offset; + + if (index >= rhub->num_ports) + continue; + + if (!is_host_mode_phy(tegra, i, j)) + continue; + + portsc = xhci_portsc_readl(rhub->ports[index]); + speed = tegra_xhci_portsc_to_speed(tegra, portsc); + tegra_xusb_padctl_enable_phy_sleepwalk(padctl, phy, speed); + tegra_xusb_padctl_enable_phy_wake(padctl, phy); + } + } +} + +static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + unsigned int i; + + for (i = 0; i < tegra->num_usb_phys; i++) { + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", i); + + if (!phy) + continue; + + if (tegra_xusb_padctl_remote_wake_detected(padctl, phy)) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + if (tegra_xusb_padctl_remote_wake_detected(padctl, tegra->phys[i])) + dev_dbg(tegra->dev, "%pOF remote wake detected\n", + tegra->phys[i]->dev.of_node); + + tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]); + } +} + +static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + unsigned int i; + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + tegra_xusb_padctl_disable_phy_sleepwalk(padctl, tegra->phys[i]); + } +} + +static void tegra_xhci_program_utmi_power_lp0_exit(struct tegra_xusb *tegra) +{ + unsigned int i, index_to_usb2; + struct phy *phy; + + for (i = 0; i < tegra->soc->num_types; i++) { + if (strcmp(tegra->soc->phy_types[i].name, "usb2") == 0) + index_to_usb2 = i; + } + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (!is_host_mode_phy(tegra, index_to_usb2, i)) + continue; + + phy = tegra_xusb_get_phy(tegra, "usb2", i); + if (tegra->lp0_utmi_pad_mask & BIT(i)) + tegra_phy_xusb_utmi_pad_power_on(phy); + else + tegra_phy_xusb_utmi_pad_power_down(phy); + } +} + +static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool is_auto_resume) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + bool wakeup = is_auto_resume ? true : device_may_wakeup(dev); + unsigned int i; + int err; + u32 usbcmd; + u32 portsc; + + dev_dbg(dev, "entering ELPG\n"); + + usbcmd = readl(&xhci->op_regs->command); + usbcmd &= ~CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + err = tegra_xusb_check_ports(tegra); + if (err < 0) { + dev_err(tegra->dev, "not all ports suspended: %d\n", err); + goto out; + } + + for (i = 0; i < xhci->usb2_rhub.num_ports; i++) { + if (!xhci->usb2_rhub.ports[i]) + continue; + portsc = xhci_portsc_readl(xhci->usb2_rhub.ports[i]); + tegra->lp0_utmi_pad_mask &= ~BIT(i); + if (((portsc & PORT_PLS_MASK) == XDEV_U3) || ((portsc & DEV_SPEED_MASK) == XDEV_FS)) + tegra->lp0_utmi_pad_mask |= BIT(i); + } + + err = xhci_suspend(xhci, wakeup); + if (err < 0) { + dev_err(tegra->dev, "failed to suspend XHCI: %d\n", err); + goto out; + } + + tegra_xusb_save_context(tegra); + + if (wakeup) + tegra_xhci_enable_phy_sleepwalk_wake(tegra); + + tegra_xusb_powergate_partitions(tegra); + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + phy_power_off(tegra->phys[i]); + if (!wakeup) + phy_exit(tegra->phys[i]); + } + + tegra_xusb_clk_disable(tegra); + +out: + if (!err) + dev_dbg(tegra->dev, "entering ELPG done\n"); + else { + usbcmd = readl(&xhci->op_regs->command); + usbcmd |= CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + dev_dbg(tegra->dev, "entering ELPG failed\n"); + pm_runtime_mark_last_busy(tegra->dev); + } + + return err; +} + +static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool is_auto_resume) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + bool wakeup = is_auto_resume ? true : device_may_wakeup(dev); + unsigned int i; + u32 usbcmd; + int err; + + dev_dbg(dev, "exiting ELPG\n"); + pm_runtime_mark_last_busy(tegra->dev); + + err = tegra_xusb_clk_enable(tegra); + if (err < 0) { + dev_err(tegra->dev, "failed to enable clocks: %d\n", err); + goto out; + } + + err = tegra_xusb_unpowergate_partitions(tegra); + if (err) + goto disable_clks; + + if (wakeup) + tegra_xhci_disable_phy_wake(tegra); + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + if (!wakeup) + phy_init(tegra->phys[i]); + + phy_power_on(tegra->phys[i]); + } + if (tegra->suspended) + tegra_xhci_program_utmi_power_lp0_exit(tegra); + + tegra_xusb_config(tegra); + tegra_xusb_restore_context(tegra); + + err = tegra_xusb_load_firmware(tegra); + if (err < 0) { + dev_err(tegra->dev, "failed to load firmware: %d\n", err); + goto disable_phy; + } + + err = __tegra_xusb_enable_firmware_messages(tegra); + if (err < 0) { + dev_err(tegra->dev, "failed to enable messages: %d\n", err); + goto disable_phy; + } + + if (wakeup) + tegra_xhci_disable_phy_sleepwalk(tegra); + + err = xhci_resume(xhci, false, is_auto_resume); + if (err < 0) { + dev_err(tegra->dev, "failed to resume XHCI: %d\n", err); + goto disable_phy; + } + + usbcmd = readl(&xhci->op_regs->command); + usbcmd |= CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + goto out; + +disable_phy: + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + phy_power_off(tegra->phys[i]); + if (!wakeup) + phy_exit(tegra->phys[i]); + } + tegra_xusb_powergate_partitions(tegra); +disable_clks: + tegra_xusb_clk_disable(tegra); +out: + if (!err) + dev_dbg(dev, "exiting ELPG done\n"); + else + dev_dbg(dev, "exiting ELPG failed\n"); + + return err; +} + +static __maybe_unused int tegra_xusb_suspend(struct device *dev) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + int err; + + synchronize_irq(tegra->mbox_irq); + + mutex_lock(&tegra->lock); + + if (pm_runtime_suspended(dev)) { + err = tegra_xusb_exit_elpg(tegra, true); + if (err < 0) + goto out; + } + + err = tegra_xusb_enter_elpg(tegra, false); + if (err < 0) { + if (pm_runtime_suspended(dev)) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + goto out; + } + +out: + if (!err) { + tegra->suspended = true; + pm_runtime_disable(dev); + + if (device_may_wakeup(dev)) { + unsigned int i; + + if (enable_irq_wake(tegra->padctl_irq)) + dev_err(dev, "failed to enable padctl wakes\n"); + + for (i = 0; i < tegra->num_wakes; i++) + enable_irq_wake(tegra->wake_irqs[i]); + } + } + + mutex_unlock(&tegra->lock); + + return err; +} + +static __maybe_unused int tegra_xusb_resume(struct device *dev) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + int err; + + mutex_lock(&tegra->lock); + + if (!tegra->suspended) { + mutex_unlock(&tegra->lock); + return 0; + } + + err = tegra_xusb_exit_elpg(tegra, false); + if (err < 0) { + mutex_unlock(&tegra->lock); + return err; + } + + if (device_may_wakeup(dev)) { + unsigned int i; + + if (disable_irq_wake(tegra->padctl_irq)) + dev_err(dev, "failed to disable padctl wakes\n"); + + for (i = 0; i < tegra->num_wakes; i++) + disable_irq_wake(tegra->wake_irqs[i]); + } + tegra->suspended = false; + mutex_unlock(&tegra->lock); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); return 0; } -#ifdef CONFIG_PM_SLEEP -static int tegra_xusb_suspend(struct device *dev) +static __maybe_unused int tegra_xusb_runtime_suspend(struct device *dev) { struct tegra_xusb *tegra = dev_get_drvdata(dev); - struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); - bool wakeup = device_may_wakeup(dev); + int ret; + + synchronize_irq(tegra->mbox_irq); + mutex_lock(&tegra->lock); + ret = tegra_xusb_enter_elpg(tegra, true); + mutex_unlock(&tegra->lock); - /* TODO: Powergate controller across suspend/resume. */ - return xhci_suspend(xhci, wakeup); + return ret; } -static int tegra_xusb_resume(struct device *dev) +static __maybe_unused int tegra_xusb_runtime_resume(struct device *dev) { struct tegra_xusb *tegra = dev_get_drvdata(dev); - struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + int err; + + mutex_lock(&tegra->lock); + err = tegra_xusb_exit_elpg(tegra, true); + mutex_unlock(&tegra->lock); - return xhci_resume(xhci, 0); + return err; } -#endif static const struct dev_pm_ops tegra_xusb_pm_ops = { SET_RUNTIME_PM_OPS(tegra_xusb_runtime_suspend, @@ -1355,11 +2507,7 @@ static const char * const tegra124_supply_names[] = { "avddio-pex", "dvddio-pex", "avdd-usb", - "avdd-pll-utmip", - "avdd-pll-erefe", - "avdd-usb-ss-pll", "hvdd-usb-ss", - "hvdd-usb-ss-pll-e", }; static const struct tegra_xusb_phy_type tegra124_phy_types[] = { @@ -1368,18 +2516,72 @@ static const struct tegra_xusb_phy_type tegra124_phy_types[] = { { .name = "hsic", .num = 2, }, }; +static const unsigned int tegra124_xusb_context_ipfs[] = { + IPFS_XUSB_HOST_MSI_BAR_SZ_0, + IPFS_XUSB_HOST_MSI_AXI_BAR_ST_0, + IPFS_XUSB_HOST_MSI_FPCI_BAR_ST_0, + IPFS_XUSB_HOST_MSI_VEC0_0, + IPFS_XUSB_HOST_MSI_EN_VEC0_0, + IPFS_XUSB_HOST_FPCI_ERROR_MASKS_0, + IPFS_XUSB_HOST_INTR_MASK_0, + IPFS_XUSB_HOST_INTR_ENABLE_0, + IPFS_XUSB_HOST_UFPCI_CONFIG_0, + IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0, + IPFS_XUSB_HOST_MCCIF_FIFOCTRL_0, +}; + +static const unsigned int tegra124_xusb_context_fpci[] = { + XUSB_CFG_ARU_CONTEXT_HS_PLS, + XUSB_CFG_ARU_CONTEXT_FS_PLS, + XUSB_CFG_ARU_CONTEXT_HSFS_SPEED, + XUSB_CFG_ARU_CONTEXT_HSFS_PP, + XUSB_CFG_ARU_CONTEXT, + XUSB_CFG_AXI_CFG, + XUSB_CFG_24, + XUSB_CFG_16, +}; + +static const struct tegra_xusb_context_soc tegra124_xusb_context = { + .ipfs = { + .num_offsets = ARRAY_SIZE(tegra124_xusb_context_ipfs), + .offsets = tegra124_xusb_context_ipfs, + }, + .fpci = { + .num_offsets = ARRAY_SIZE(tegra124_xusb_context_fpci), + .offsets = tegra124_xusb_context_fpci, + }, +}; + +static const struct tegra_xusb_soc_ops tegra124_ops = { + .mbox_reg_readl = &fpci_readl, + .mbox_reg_writel = &fpci_writel, + .csb_reg_readl = &fpci_csb_readl, + .csb_reg_writel = &fpci_csb_writel, +}; + static const struct tegra_xusb_soc tegra124_soc = { .firmware = "nvidia/tegra124/xusb.bin", .supply_names = tegra124_supply_names, .num_supplies = ARRAY_SIZE(tegra124_supply_names), .phy_types = tegra124_phy_types, .num_types = ARRAY_SIZE(tegra124_phy_types), + .context = &tegra124_xusb_context, .ports = { .usb2 = { .offset = 4, .count = 4, }, .hsic = { .offset = 6, .count = 2, }, .usb3 = { .offset = 0, .count = 2, }, }, .scale_ss_clock = true, + .has_ipfs = true, + .otg_reset_sspi = false, + .ops = &tegra124_ops, + .mbox = { + .cmd = 0xe4, + .data_in = 0xe8, + .data_out = 0xec, + .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, + }, }; MODULE_FIRMWARE("nvidia/tegra124/xusb.bin"); @@ -1387,10 +2589,6 @@ static const char * const tegra210_supply_names[] = { "dvddio-pex", "hvddio-pex", "avdd-usb", - "avdd-pll-utmip", - "avdd-pll-uerefe", - "dvdd-pex-pll", - "hvdd-pex-pll-e", }; static const struct tegra_xusb_phy_type tegra210_phy_types[] = { @@ -1405,18 +2603,142 @@ static const struct tegra_xusb_soc tegra210_soc = { .num_supplies = ARRAY_SIZE(tegra210_supply_names), .phy_types = tegra210_phy_types, .num_types = ARRAY_SIZE(tegra210_phy_types), + .context = &tegra124_xusb_context, .ports = { .usb2 = { .offset = 4, .count = 4, }, .hsic = { .offset = 8, .count = 1, }, .usb3 = { .offset = 0, .count = 4, }, }, .scale_ss_clock = false, + .has_ipfs = true, + .otg_reset_sspi = true, + .ops = &tegra124_ops, + .mbox = { + .cmd = 0xe4, + .data_in = 0xe8, + .data_out = 0xec, + .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, + }, }; MODULE_FIRMWARE("nvidia/tegra210/xusb.bin"); +static const char * const tegra186_supply_names[] = { +}; +MODULE_FIRMWARE("nvidia/tegra186/xusb.bin"); + +static const struct tegra_xusb_phy_type tegra186_phy_types[] = { + { .name = "usb3", .num = 3, }, + { .name = "usb2", .num = 3, }, + { .name = "hsic", .num = 1, }, +}; + +static const struct tegra_xusb_context_soc tegra186_xusb_context = { + .fpci = { + .num_offsets = ARRAY_SIZE(tegra124_xusb_context_fpci), + .offsets = tegra124_xusb_context_fpci, + }, +}; + +static const struct tegra_xusb_soc tegra186_soc = { + .firmware = "nvidia/tegra186/xusb.bin", + .supply_names = tegra186_supply_names, + .num_supplies = ARRAY_SIZE(tegra186_supply_names), + .phy_types = tegra186_phy_types, + .num_types = ARRAY_SIZE(tegra186_phy_types), + .context = &tegra186_xusb_context, + .ports = { + .usb3 = { .offset = 0, .count = 3, }, + .usb2 = { .offset = 3, .count = 3, }, + .hsic = { .offset = 6, .count = 1, }, + }, + .scale_ss_clock = false, + .has_ipfs = false, + .otg_reset_sspi = false, + .ops = &tegra124_ops, + .mbox = { + .cmd = 0xe4, + .data_in = 0xe8, + .data_out = 0xec, + .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, + }, + .lpm_support = true, +}; + +static const char * const tegra194_supply_names[] = { +}; + +static const struct tegra_xusb_phy_type tegra194_phy_types[] = { + { .name = "usb3", .num = 4, }, + { .name = "usb2", .num = 4, }, +}; + +static const struct tegra_xusb_soc tegra194_soc = { + .firmware = "nvidia/tegra194/xusb.bin", + .supply_names = tegra194_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_supply_names), + .phy_types = tegra194_phy_types, + .num_types = ARRAY_SIZE(tegra194_phy_types), + .context = &tegra186_xusb_context, + .ports = { + .usb3 = { .offset = 0, .count = 4, }, + .usb2 = { .offset = 4, .count = 4, }, + }, + .scale_ss_clock = false, + .has_ipfs = false, + .otg_reset_sspi = false, + .ops = &tegra124_ops, + .mbox = { + .cmd = 0x68, + .data_in = 0x6c, + .data_out = 0x70, + .owner = 0x74, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, + }, + .lpm_support = true, +}; +MODULE_FIRMWARE("nvidia/tegra194/xusb.bin"); + +static const struct tegra_xusb_soc_ops tegra234_ops = { + .mbox_reg_readl = &bar2_readl, + .mbox_reg_writel = &bar2_writel, + .csb_reg_readl = &bar2_csb_readl, + .csb_reg_writel = &bar2_csb_writel, +}; + +static const struct tegra_xusb_soc tegra234_soc = { + .supply_names = tegra194_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_supply_names), + .phy_types = tegra194_phy_types, + .num_types = ARRAY_SIZE(tegra194_phy_types), + .max_num_wakes = 7, + .context = &tegra186_xusb_context, + .ports = { + .usb3 = { .offset = 0, .count = 4, }, + .usb2 = { .offset = 4, .count = 4, }, + }, + .scale_ss_clock = false, + .has_ipfs = false, + .otg_reset_sspi = false, + .ops = &tegra234_ops, + .mbox = { + .cmd = XUSB_BAR2_ARU_MBOX_CMD, + .data_in = XUSB_BAR2_ARU_MBOX_DATA_IN, + .data_out = XUSB_BAR2_ARU_MBOX_DATA_OUT, + .owner = XUSB_BAR2_ARU_MBOX_OWNER, + .smi_intr = XUSB_BAR2_ARU_SMI_INTR, + }, + .lpm_support = true, + .has_bar2 = true, +}; + static const struct of_device_id tegra_xusb_of_match[] = { { .compatible = "nvidia,tegra124-xusb", .data = &tegra124_soc }, { .compatible = "nvidia,tegra210-xusb", .data = &tegra210_soc }, + { .compatible = "nvidia,tegra186-xusb", .data = &tegra186_soc }, + { .compatible = "nvidia,tegra194-xusb", .data = &tegra194_soc }, + { .compatible = "nvidia,tegra234-xusb", .data = &tegra234_soc }, { }, }; MODULE_DEVICE_TABLE(of, tegra_xusb_of_match); @@ -1424,6 +2746,7 @@ MODULE_DEVICE_TABLE(of, tegra_xusb_of_match); static struct platform_driver tegra_xusb_driver = { .probe = tegra_xusb_probe, .remove = tegra_xusb_remove, + .shutdown = tegra_xusb_shutdown, .driver = { .name = "tegra-xusb", .pm = &tegra_xusb_pm_ops, @@ -1433,7 +2756,10 @@ static struct platform_driver tegra_xusb_driver = { static void tegra_xhci_quirks(struct device *dev, struct xhci_hcd *xhci) { - xhci->quirks |= XHCI_PLAT; + struct tegra_xusb *tegra = dev_get_drvdata(dev); + + if (tegra && tegra->soc->lpm_support) + xhci->quirks |= XHCI_LPM_SUPPORT; } static int tegra_xhci_setup(struct usb_hcd *hcd) @@ -1441,8 +2767,84 @@ static int tegra_xhci_setup(struct usb_hcd *hcd) return xhci_gen_setup(hcd, tegra_xhci_quirks); } +static int tegra_xhci_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index, + char *buf, u16 length) +{ + struct tegra_xusb *tegra = dev_get_drvdata(hcd->self.controller); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_hub *rhub; + struct xhci_bus_state *bus_state; + int port = (index & 0xff) - 1; + unsigned int i; + struct xhci_port **ports; + u32 portsc; + int ret; + struct phy *phy; + + rhub = &xhci->usb2_rhub; + bus_state = &rhub->bus_state; + if (bus_state->resuming_ports && hcd->speed == HCD_USB2) { + ports = rhub->ports; + i = rhub->num_ports; + while (i--) { + if (!test_bit(i, &bus_state->resuming_ports)) + continue; + portsc = xhci_portsc_readl(ports[i]); + if ((portsc & PORT_PLS_MASK) == XDEV_RESUME) + tegra_phy_xusb_utmi_pad_power_on( + tegra_xusb_get_phy(tegra, "usb2", (int) i)); + } + } + + if (hcd->speed == HCD_USB2) { + phy = tegra_xusb_get_phy(tegra, "usb2", port); + if ((type_req == ClearPortFeature) && (value == USB_PORT_FEAT_SUSPEND)) { + if (!index || index > rhub->num_ports) + return -EPIPE; + tegra_phy_xusb_utmi_pad_power_on(phy); + } + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_RESET)) { + if (!index || index > rhub->num_ports) + return -EPIPE; + ports = rhub->ports; + portsc = xhci_portsc_readl(ports[port]); + if (portsc & PORT_CONNECT) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + } + + ret = xhci_hub_control(hcd, type_req, value, index, buf, length); + if (ret < 0) + return ret; + + if (hcd->speed == HCD_USB2) { + /* Use phy where we set previously */ + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_SUSPEND)) + /* We don't suspend the PAD while HNP role swap happens on the OTG port */ + if (!((hcd->self.otg_port == (port + 1)) && hcd->self.b_hnp_enable)) + tegra_phy_xusb_utmi_pad_power_down(phy); + + if ((type_req == ClearPortFeature) && (value == USB_PORT_FEAT_C_CONNECTION)) { + ports = rhub->ports; + portsc = xhci_portsc_readl(ports[port]); + if (!(portsc & PORT_CONNECT)) { + /* We don't suspend the PAD while HNP role swap happens on the OTG + * port + */ + if (!((hcd->self.otg_port == (port + 1)) && hcd->self.b_hnp_enable)) + tegra_phy_xusb_utmi_pad_power_down(phy); + } + } + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_TEST)) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + + return ret; +} + static const struct xhci_driver_overrides tegra_xhci_overrides __initconst = { .reset = tegra_xhci_setup, + .hub_control = tegra_xhci_hub_control, }; static int __init tegra_xusb_init(void) |
