Skip to content

Commit

Permalink
usb: host: xhci-tegra: Add support for XUSB context save/restore
Browse files Browse the repository at this point in the history
The XUSB controller contains registers that need to be saved on suspend
and restored on resume in addition to the XHCI specific registers. Add
support for saving and restoring the XUSB specific context.

Based on work by JC Kuo <jckuo@nvidia.com>.

Signed-off-by: Thierry Reding <treding@nvidia.com>
Link: https://lore.kernel.org/r/20191206140653.2085561-9-thierry.reding@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Thierry Reding authored and Greg Kroah-Hartman committed Dec 10, 2019
1 parent 1792692 commit 5c4e8d3
Showing 1 changed file with 100 additions and 2 deletions.
102 changes: 100 additions & 2 deletions drivers/usb/host/xhci-tegra.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,25 @@ struct tegra_xusb_mbox_regs {
u16 owner;
};

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_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;
const struct tegra_xusb_context_soc *context;

struct {
struct {
Expand All @@ -175,6 +188,11 @@ struct tegra_xusb_soc {
bool has_ipfs;
};

struct tegra_xusb_context {
u32 *ipfs;
u32 *fpci;
};

struct tegra_xusb {
struct device *dev;
void __iomem *regs;
Expand Down Expand Up @@ -221,6 +239,8 @@ struct tegra_xusb {
void *virt;
dma_addr_t phys;
} fw;

struct tegra_xusb_context context;
};

static struct hc_driver __read_mostly tegra_xhci_hc_driver;
Expand Down Expand Up @@ -796,6 +816,37 @@ static int tegra_xusb_runtime_resume(struct device *dev)
return err;
}

#ifdef CONFIG_PM_SLEEP
static int tegra_xusb_init_context(struct tegra_xusb *tegra)
{
const struct tegra_xusb_context_soc *soc = tegra->soc->context;

/*
* Skip support for context save/restore if the SoC doesn't have any
* XUSB specific context that needs to be saved/restored.
*/
if (!soc)
return 0;

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->ipfs.num_offsets,
sizeof(u32), GFP_KERNEL);
if (!tegra->context.fpci)
return -ENOMEM;

return 0;
}
#else
static inline int tegra_xusb_init_context(struct tegra_xusb *tegra)
{
return 0;
}
#endif

static int tegra_xusb_request_firmware(struct tegra_xusb *tegra)
{
struct tegra_xusb_fw_header *header;
Expand Down Expand Up @@ -1039,6 +1090,10 @@ static int tegra_xusb_probe(struct platform_device *pdev)
mutex_init(&tegra->lock);
tegra->dev = &pdev->dev;

err = tegra_xusb_init_context(tegra);
if (err < 0)
return err;

regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tegra->regs = devm_ioremap_resource(&pdev->dev, regs);
if (IS_ERR(tegra->regs))
Expand Down Expand Up @@ -1382,22 +1437,65 @@ static int tegra_xusb_remove(struct platform_device *pdev)
}

#ifdef CONFIG_PM_SLEEP
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 && 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 && 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 && 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 && 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 int tegra_xusb_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 err;

/* TODO: Powergate controller across suspend/resume. */
return xhci_suspend(xhci, wakeup);
err = xhci_suspend(xhci, wakeup);
if (err < 0)
return err;

tegra_xusb_save_context(tegra);

return 0;
}

static int tegra_xusb_resume(struct device *dev)
{
struct tegra_xusb *tegra = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);

return xhci_resume(xhci, 0);
tegra_xusb_restore_context(tegra);

return xhci_resume(xhci, false);
}
#endif

Expand Down

0 comments on commit 5c4e8d3

Please sign in to comment.