Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d17ee77b authored by Gregory Herrero's avatar Gregory Herrero Committed by Felipe Balbi
Browse files

usb: dwc2: add controller hibernation support



When suspending usb bus, phy driver may disable controller power.
In this case, registers need to be saved on suspend and restored
on resume.

Acked-by: default avatarJohn Youn <johnyoun@synopsys.com>
Signed-off-by: default avatarGregory Herrero <gregory.herrero@intel.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent 563cf017
Loading
Loading
Loading
Loading
+377 −0
Original line number Diff line number Diff line
@@ -56,6 +56,383 @@
#include "core.h"
#include "hcd.h"

#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
/**
 * dwc2_backup_host_registers() - Backup controller host registers.
 * When suspending usb bus, registers needs to be backuped
 * if controller power is disabled once suspended.
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
static int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg)
{
	struct dwc2_hregs_backup *hr;
	int i;

	dev_dbg(hsotg->dev, "%s\n", __func__);

	/* Backup Host regs */
	hr = hsotg->hr_backup;
	if (!hr) {
		hr = devm_kzalloc(hsotg->dev, sizeof(*hr), GFP_KERNEL);
		if (!hr) {
			dev_err(hsotg->dev, "%s: can't allocate host regs\n",
					__func__);
			return -ENOMEM;
		}

		hsotg->hr_backup = hr;
	}
	hr->hcfg = readl(hsotg->regs + HCFG);
	hr->haintmsk = readl(hsotg->regs + HAINTMSK);
	for (i = 0; i < hsotg->core_params->host_channels; ++i)
		hr->hcintmsk[i] = readl(hsotg->regs + HCINTMSK(i));

	hr->hprt0 = readl(hsotg->regs + HPRT0);
	hr->hfir = readl(hsotg->regs + HFIR);

	return 0;
}

/**
 * dwc2_restore_host_registers() - Restore controller host registers.
 * When resuming usb bus, device registers needs to be restored
 * if controller power were disabled.
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
static int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg)
{
	struct dwc2_hregs_backup *hr;
	int i;

	dev_dbg(hsotg->dev, "%s\n", __func__);

	/* Restore host regs */
	hr = hsotg->hr_backup;
	if (!hr) {
		dev_err(hsotg->dev, "%s: no host registers to restore\n",
				__func__);
		return -EINVAL;
	}

	writel(hr->hcfg, hsotg->regs + HCFG);
	writel(hr->haintmsk, hsotg->regs + HAINTMSK);

	for (i = 0; i < hsotg->core_params->host_channels; ++i)
		writel(hr->hcintmsk[i], hsotg->regs + HCINTMSK(i));

	writel(hr->hprt0, hsotg->regs + HPRT0);
	writel(hr->hfir, hsotg->regs + HFIR);

	return 0;
}
#else
static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg)
{ return 0; }

static inline int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg)
{ return 0; }
#endif

#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
	IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
/**
 * dwc2_backup_device_registers() - Backup controller device registers.
 * When suspending usb bus, registers needs to be backuped
 * if controller power is disabled once suspended.
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
static int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg)
{
	struct dwc2_dregs_backup *dr;
	int i;

	dev_dbg(hsotg->dev, "%s\n", __func__);

	/* Backup dev regs */
	dr = hsotg->dr_backup;
	if (!dr) {
		dr = devm_kzalloc(hsotg->dev, sizeof(*dr), GFP_KERNEL);
		if (!dr) {
			dev_err(hsotg->dev, "%s: can't allocate device regs\n",
					__func__);
			return -ENOMEM;
		}

		hsotg->dr_backup = dr;
	}

	dr->dcfg = readl(hsotg->regs + DCFG);
	dr->dctl = readl(hsotg->regs + DCTL);
	dr->daintmsk = readl(hsotg->regs + DAINTMSK);
	dr->diepmsk = readl(hsotg->regs + DIEPMSK);
	dr->doepmsk = readl(hsotg->regs + DOEPMSK);

	for (i = 0; i < hsotg->num_of_eps; i++) {
		/* Backup IN EPs */
		dr->diepctl[i] = readl(hsotg->regs + DIEPCTL(i));

		/* Ensure DATA PID is correctly configured */
		if (dr->diepctl[i] & DXEPCTL_DPID)
			dr->diepctl[i] |= DXEPCTL_SETD1PID;
		else
			dr->diepctl[i] |= DXEPCTL_SETD0PID;

		dr->dieptsiz[i] = readl(hsotg->regs + DIEPTSIZ(i));
		dr->diepdma[i] = readl(hsotg->regs + DIEPDMA(i));

		/* Backup OUT EPs */
		dr->doepctl[i] = readl(hsotg->regs + DOEPCTL(i));

		/* Ensure DATA PID is correctly configured */
		if (dr->doepctl[i] & DXEPCTL_DPID)
			dr->doepctl[i] |= DXEPCTL_SETD1PID;
		else
			dr->doepctl[i] |= DXEPCTL_SETD0PID;

		dr->doeptsiz[i] = readl(hsotg->regs + DOEPTSIZ(i));
		dr->doepdma[i] = readl(hsotg->regs + DOEPDMA(i));
	}

	return 0;
}

/**
 * dwc2_restore_device_registers() - Restore controller device registers.
 * When resuming usb bus, device registers needs to be restored
 * if controller power were disabled.
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
static int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg)
{
	struct dwc2_dregs_backup *dr;
	u32 dctl;
	int i;

	dev_dbg(hsotg->dev, "%s\n", __func__);

	/* Restore dev regs */
	dr = hsotg->dr_backup;
	if (!dr) {
		dev_err(hsotg->dev, "%s: no device registers to restore\n",
				__func__);
		return -EINVAL;
	}

	writel(dr->dcfg, hsotg->regs + DCFG);
	writel(dr->dctl, hsotg->regs + DCTL);
	writel(dr->daintmsk, hsotg->regs + DAINTMSK);
	writel(dr->diepmsk, hsotg->regs + DIEPMSK);
	writel(dr->doepmsk, hsotg->regs + DOEPMSK);

	for (i = 0; i < hsotg->num_of_eps; i++) {
		/* Restore IN EPs */
		writel(dr->diepctl[i], hsotg->regs + DIEPCTL(i));
		writel(dr->dieptsiz[i], hsotg->regs + DIEPTSIZ(i));
		writel(dr->diepdma[i], hsotg->regs + DIEPDMA(i));

		/* Restore OUT EPs */
		writel(dr->doepctl[i], hsotg->regs + DOEPCTL(i));
		writel(dr->doeptsiz[i], hsotg->regs + DOEPTSIZ(i));
		writel(dr->doepdma[i], hsotg->regs + DOEPDMA(i));
	}

	/* Set the Power-On Programming done bit */
	dctl = readl(hsotg->regs + DCTL);
	dctl |= DCTL_PWRONPRGDONE;
	writel(dctl, hsotg->regs + DCTL);

	return 0;
}
#else
static inline int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg)
{ return 0; }

static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg)
{ return 0; }
#endif

/**
 * dwc2_backup_global_registers() - Backup global controller registers.
 * When suspending usb bus, registers needs to be backuped
 * if controller power is disabled once suspended.
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
static int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg)
{
	struct dwc2_gregs_backup *gr;
	int i;

	/* Backup global regs */
	gr = hsotg->gr_backup;
	if (!gr) {
		gr = devm_kzalloc(hsotg->dev, sizeof(*gr), GFP_KERNEL);
		if (!gr) {
			dev_err(hsotg->dev, "%s: can't allocate global regs\n",
					__func__);
			return -ENOMEM;
		}

		hsotg->gr_backup = gr;
	}

	gr->gotgctl = readl(hsotg->regs + GOTGCTL);
	gr->gintmsk = readl(hsotg->regs + GINTMSK);
	gr->gahbcfg = readl(hsotg->regs + GAHBCFG);
	gr->gusbcfg = readl(hsotg->regs + GUSBCFG);
	gr->grxfsiz = readl(hsotg->regs + GRXFSIZ);
	gr->gnptxfsiz = readl(hsotg->regs + GNPTXFSIZ);
	gr->hptxfsiz = readl(hsotg->regs + HPTXFSIZ);
	gr->gdfifocfg = readl(hsotg->regs + GDFIFOCFG);
	for (i = 0; i < MAX_EPS_CHANNELS; i++)
		gr->dtxfsiz[i] = readl(hsotg->regs + DPTXFSIZN(i));

	return 0;
}

/**
 * dwc2_restore_global_registers() - Restore controller global registers.
 * When resuming usb bus, device registers needs to be restored
 * if controller power were disabled.
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
static int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg)
{
	struct dwc2_gregs_backup *gr;
	int i;

	dev_dbg(hsotg->dev, "%s\n", __func__);

	/* Restore global regs */
	gr = hsotg->gr_backup;
	if (!gr) {
		dev_err(hsotg->dev, "%s: no global registers to restore\n",
				__func__);
		return -EINVAL;
	}

	writel(0xffffffff, hsotg->regs + GINTSTS);
	writel(gr->gotgctl, hsotg->regs + GOTGCTL);
	writel(gr->gintmsk, hsotg->regs + GINTMSK);
	writel(gr->gusbcfg, hsotg->regs + GUSBCFG);
	writel(gr->gahbcfg, hsotg->regs + GAHBCFG);
	writel(gr->grxfsiz, hsotg->regs + GRXFSIZ);
	writel(gr->gnptxfsiz, hsotg->regs + GNPTXFSIZ);
	writel(gr->hptxfsiz, hsotg->regs + HPTXFSIZ);
	writel(gr->gdfifocfg, hsotg->regs + GDFIFOCFG);
	for (i = 0; i < MAX_EPS_CHANNELS; i++)
		writel(gr->dtxfsiz[i], hsotg->regs + DPTXFSIZN(i));

	return 0;
}

/**
 * dwc2_exit_hibernation() - Exit controller from Partial Power Down.
 *
 * @hsotg: Programming view of the DWC_otg controller
 * @restore: Controller registers need to be restored
 */
int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore)
{
	u32 pcgcctl;
	int ret = 0;

	pcgcctl = readl(hsotg->regs + PCGCTL);
	pcgcctl &= ~PCGCTL_STOPPCLK;
	writel(pcgcctl, hsotg->regs + PCGCTL);

	pcgcctl = readl(hsotg->regs + PCGCTL);
	pcgcctl &= ~PCGCTL_PWRCLMP;
	writel(pcgcctl, hsotg->regs + PCGCTL);

	pcgcctl = readl(hsotg->regs + PCGCTL);
	pcgcctl &= ~PCGCTL_RSTPDWNMODULE;
	writel(pcgcctl, hsotg->regs + PCGCTL);

	udelay(100);
	if (restore) {
		ret = dwc2_restore_global_registers(hsotg);
		if (ret) {
			dev_err(hsotg->dev, "%s: failed to restore registers\n",
					__func__);
			return ret;
		}
		if (dwc2_is_host_mode(hsotg)) {
			ret = dwc2_restore_host_registers(hsotg);
			if (ret) {
				dev_err(hsotg->dev, "%s: failed to restore host registers\n",
						__func__);
				return ret;
			}
		} else {
			ret = dwc2_restore_device_registers(hsotg);
			if (ret) {
				dev_err(hsotg->dev, "%s: failed to restore device registers\n",
						__func__);
				return ret;
			}
		}
	}

	return ret;
}

/**
 * dwc2_enter_hibernation() - Put controller in Partial Power Down.
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg)
{
	u32 pcgcctl;
	int ret = 0;

	/* Backup all registers */
	ret = dwc2_backup_global_registers(hsotg);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to backup global registers\n",
				__func__);
		return ret;
	}

	if (dwc2_is_host_mode(hsotg)) {
		ret = dwc2_backup_host_registers(hsotg);
		if (ret) {
			dev_err(hsotg->dev, "%s: failed to backup host registers\n",
					__func__);
			return ret;
		}
	} else {
		ret = dwc2_backup_device_registers(hsotg);
		if (ret) {
			dev_err(hsotg->dev, "%s: failed to backup device registers\n",
					__func__);
			return ret;
		}
	}

	/* Put the controller in low power state */
	pcgcctl = readl(hsotg->regs + PCGCTL);

	pcgcctl |= PCGCTL_PWRCLMP;
	writel(pcgcctl, hsotg->regs + PCGCTL);
	ndelay(20);

	pcgcctl |= PCGCTL_RSTPDWNMODULE;
	writel(pcgcctl, hsotg->regs + PCGCTL);
	ndelay(20);

	pcgcctl |= PCGCTL_STOPPCLK;
	writel(pcgcctl, hsotg->regs + PCGCTL);

	return ret;
}

/**
 * dwc2_enable_common_interrupts() - Initializes the commmon interrupts,
 * used in both device and host modes
+84 −0
Original line number Diff line number Diff line
@@ -451,6 +451,82 @@ struct dwc2_hw_params {
/* Size of control and EP0 buffers */
#define DWC2_CTRL_BUFF_SIZE 8

/**
 * struct dwc2_gregs_backup - Holds global registers state before entering partial
 * power down
 * @gotgctl:		Backup of GOTGCTL register
 * @gintmsk:		Backup of GINTMSK register
 * @gahbcfg:		Backup of GAHBCFG register
 * @gusbcfg:		Backup of GUSBCFG register
 * @grxfsiz:		Backup of GRXFSIZ register
 * @gnptxfsiz:		Backup of GNPTXFSIZ register
 * @gi2cctl:		Backup of GI2CCTL register
 * @hptxfsiz:		Backup of HPTXFSIZ register
 * @gdfifocfg:		Backup of GDFIFOCFG register
 * @dtxfsiz:		Backup of DTXFSIZ registers for each endpoint
 * @gpwrdn:		Backup of GPWRDN register
 */
struct dwc2_gregs_backup {
	u32 gotgctl;
	u32 gintmsk;
	u32 gahbcfg;
	u32 gusbcfg;
	u32 grxfsiz;
	u32 gnptxfsiz;
	u32 gi2cctl;
	u32 hptxfsiz;
	u32 pcgcctl;
	u32 gdfifocfg;
	u32 dtxfsiz[MAX_EPS_CHANNELS];
	u32 gpwrdn;
};

/**
 * struct  dwc2_dregs_backup - Holds device registers state before entering partial
 * power down
 * @dcfg:		Backup of DCFG register
 * @dctl:		Backup of DCTL register
 * @daintmsk:		Backup of DAINTMSK register
 * @diepmsk:		Backup of DIEPMSK register
 * @doepmsk:		Backup of DOEPMSK register
 * @diepctl:		Backup of DIEPCTL register
 * @dieptsiz:		Backup of DIEPTSIZ register
 * @diepdma:		Backup of DIEPDMA register
 * @doepctl:		Backup of DOEPCTL register
 * @doeptsiz:		Backup of DOEPTSIZ register
 * @doepdma:		Backup of DOEPDMA register
 */
struct dwc2_dregs_backup {
	u32 dcfg;
	u32 dctl;
	u32 daintmsk;
	u32 diepmsk;
	u32 doepmsk;
	u32 diepctl[MAX_EPS_CHANNELS];
	u32 dieptsiz[MAX_EPS_CHANNELS];
	u32 diepdma[MAX_EPS_CHANNELS];
	u32 doepctl[MAX_EPS_CHANNELS];
	u32 doeptsiz[MAX_EPS_CHANNELS];
	u32 doepdma[MAX_EPS_CHANNELS];
};

/**
 * struct  dwc2_hregs_backup - Holds host registers state before entering partial
 * power down
 * @hcfg:		Backup of HCFG register
 * @haintmsk:		Backup of HAINTMSK register
 * @hcintmsk:		Backup of HCINTMSK register
 * @hptr0:		Backup of HPTR0 register
 * @hfir:		Backup of HFIR register
 */
struct dwc2_hregs_backup {
	u32 hcfg;
	u32 haintmsk;
	u32 hcintmsk[MAX_EPS_CHANNELS];
	u32 hprt0;
	u32 hfir;
};

/**
 * struct dwc2_hsotg - Holds the state of the driver, including the non-periodic
 * and periodic schedules
@@ -481,6 +557,9 @@ struct dwc2_hw_params {
 *                      interrupt
 * @wkp_timer:          Timer object for handling Wakeup Detected interrupt
 * @lx_state:           Lx state of connected device
 * @gregs_backup: Backup of global registers during suspend
 * @dregs_backup: Backup of device registers during suspend
 * @hregs_backup: Backup of host registers during suspend
 *
 * These are for host mode:
 *
@@ -613,6 +692,9 @@ struct dwc2_hsotg {
	struct work_struct wf_otg;
	struct timer_list wkp_timer;
	enum dwc2_lx_state lx_state;
	struct dwc2_gregs_backup *gr_backup;
	struct dwc2_dregs_backup *dr_backup;
	struct dwc2_hregs_backup *hr_backup;

	struct dentry *debug_root;
	struct debugfs_regset32 *regset;
@@ -749,6 +831,8 @@ enum dwc2_halt_status {
 * and the DWC_otg controller
 */
extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg);
extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg);
extern int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore);

/*
 * Host core Functions.