summaryrefslogtreecommitdiffstats
path: root/drivers/usb/chipidea
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2015-04-20 03:32:26 +0200
committerDave Airlie <airlied@redhat.com>2015-04-20 05:05:20 +0200
commit2c33ce009ca2389dbf0535d0672214d09738e35e (patch)
tree6186a6458c3c160385d794a23eaf07c786a9e61b /drivers/usb/chipidea
parentmedia-bus: Fixup RGB444_1X12, RGB565_1X16, and YUV8_1X24 media bus format (diff)
parentMerge branch 'turbostat' of git://git.kernel.org/pub/scm/linux/kernel/git/len... (diff)
downloadlinux-2c33ce009ca2389dbf0535d0672214d09738e35e.tar.xz
linux-2c33ce009ca2389dbf0535d0672214d09738e35e.zip
Merge Linus master into drm-next
The merge is clean, but the arm build fails afterwards, due to API changes in the regulator tree. I've included the patch into the merge to fix the build. Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r--drivers/usb/chipidea/Kconfig11
-rw-r--r--drivers/usb/chipidea/Makefile9
-rw-r--r--drivers/usb/chipidea/bits.h11
-rw-r--r--drivers/usb/chipidea/ci.h69
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c153
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.h1
-rw-r--r--drivers/usb/chipidea/ci_hdrc_pci.c31
-rw-r--r--drivers/usb/chipidea/ci_hdrc_zevio.c2
-rw-r--r--drivers/usb/chipidea/core.c185
-rw-r--r--drivers/usb/chipidea/debug.c4
-rw-r--r--drivers/usb/chipidea/host.c74
-rw-r--r--drivers/usb/chipidea/otg.c2
-rw-r--r--drivers/usb/chipidea/otg_fsm.c389
-rw-r--r--drivers/usb/chipidea/otg_fsm.h27
-rw-r--r--drivers/usb/chipidea/udc.c41
-rw-r--r--drivers/usb/chipidea/usbmisc_imx.c129
16 files changed, 816 insertions, 322 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig
index 77b47d82c9a6..5ce3f1d6a6ed 100644
--- a/drivers/usb/chipidea/Kconfig
+++ b/drivers/usb/chipidea/Kconfig
@@ -10,6 +10,17 @@ config USB_CHIPIDEA
if USB_CHIPIDEA
+config USB_CHIPIDEA_OF
+ tristate
+ depends on OF
+ default USB_CHIPIDEA
+
+config USB_CHIPIDEA_PCI
+ tristate
+ depends on PCI
+ depends on NOP_USB_XCEIV
+ default USB_CHIPIDEA
+
config USB_CHIPIDEA_UDC
bool "ChipIdea device controller"
depends on USB_GADGET
diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile
index 1fc86a2ca22d..4decb12f2578 100644
--- a/drivers/usb/chipidea/Makefile
+++ b/drivers/usb/chipidea/Makefile
@@ -14,11 +14,6 @@ obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_usb2.o
obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_msm.o
obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_zevio.o
-# PCI doesn't provide stubs, need to check
-ifneq ($(CONFIG_PCI),)
- obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_pci.o
-endif
+obj-$(CONFIG_USB_CHIPIDEA_PCI) += ci_hdrc_pci.o
-ifneq ($(CONFIG_OF),)
- obj-$(CONFIG_USB_CHIPIDEA) += usbmisc_imx.o ci_hdrc_imx.o
-endif
+obj-$(CONFIG_USB_CHIPIDEA_OF) += usbmisc_imx.o ci_hdrc_imx.o
diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h
index ca57e3dcd3d5..3cb9bda51ddf 100644
--- a/drivers/usb/chipidea/bits.h
+++ b/drivers/usb/chipidea/bits.h
@@ -15,6 +15,16 @@
#include <linux/usb/ehci_def.h>
+/*
+ * ID
+ * For 1.x revision, bit24 - bit31 are reserved
+ * For 2.x revision, bit25 - bit28 are 0x2
+ */
+#define TAG (0x1F << 16)
+#define REVISION (0xF << 21)
+#define VERSION (0xF << 25)
+#define CIVERSION (0x7 << 29)
+
/* HCCPARAMS */
#define HCCPARAMS_LEN BIT(17)
@@ -53,6 +63,7 @@
#define PORTSC_HSP BIT(9)
#define PORTSC_PP BIT(12)
#define PORTSC_PTC (0x0FUL << 16)
+#define PORTSC_WKCN BIT(20)
#define PORTSC_PHCD(d) ((d) ? BIT(22) : BIT(23))
/* PTS and PTW for non lpm version only */
#define PORTSC_PFSC BIT(24)
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 65913d48f0c8..6d6200e37b71 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -29,6 +29,15 @@
/******************************************************************************
* REGISTERS
*****************************************************************************/
+/* Identification Registers */
+#define ID_ID 0x0
+#define ID_HWGENERAL 0x4
+#define ID_HWHOST 0x8
+#define ID_HWDEVICE 0xc
+#define ID_HWTXBUF 0x10
+#define ID_HWRXBUF 0x14
+#define ID_SBUSCFG 0x90
+
/* register indices */
enum ci_hw_regs {
CAP_CAPLENGTH,
@@ -97,6 +106,18 @@ enum ci_role {
CI_ROLE_END,
};
+enum ci_revision {
+ CI_REVISION_1X = 10, /* Revision 1.x */
+ CI_REVISION_20 = 20, /* Revision 2.0 */
+ CI_REVISION_21, /* Revision 2.1 */
+ CI_REVISION_22, /* Revision 2.2 */
+ CI_REVISION_23, /* Revision 2.3 */
+ CI_REVISION_24, /* Revision 2.4 */
+ CI_REVISION_25, /* Revision 2.5 */
+ CI_REVISION_25_PLUS, /* Revision above than 2.5 */
+ CI_REVISION_UNKNOWN = 99, /* Unknown Revision */
+};
+
/**
* struct ci_role_driver - host/gadget role driver
* @start: start this role
@@ -141,7 +162,10 @@ struct hw_bank {
* @role: current role
* @is_otg: if the device is otg-capable
* @fsm: otg finite state machine
- * @fsm_timer: pointer to timer list of otg fsm
+ * @otg_fsm_hrtimer: hrtimer for otg fsm timers
+ * @hr_timeouts: time out list for active otg fsm timers
+ * @enabled_otg_timer_bits: bits of enabled otg timers
+ * @next_otg_timer: next nearest enabled timer to be expired
* @work: work for role changing
* @wq: workqueue thread
* @qh_pool: allocation pool for queue heads
@@ -169,6 +193,10 @@ struct hw_bank {
* @b_sess_valid_event: indicates there is a vbus event, and handled
* at ci_otg_work
* @imx28_write_fix: Freescale imx28 needs swp instruction for writing
+ * @supports_runtime_pm: if runtime pm is supported
+ * @in_lpm: if the core in low power mode
+ * @wakeup_int: if wakeup interrupt occur
+ * @rev: The revision number for controller
*/
struct ci_hdrc {
struct device *dev;
@@ -180,7 +208,10 @@ struct ci_hdrc {
bool is_otg;
struct usb_otg otg;
struct otg_fsm fsm;
- struct ci_otg_fsm_timer_list *fsm_timer;
+ struct hrtimer otg_fsm_hrtimer;
+ ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS];
+ unsigned enabled_otg_timer_bits;
+ enum otg_fsm_timer next_otg_timer;
struct work_struct work;
struct workqueue_struct *wq;
@@ -211,6 +242,10 @@ struct ci_hdrc {
bool id_event;
bool b_sess_valid_event;
bool imx28_write_fix;
+ bool supports_runtime_pm;
+ bool in_lpm;
+ bool wakeup_int;
+ enum ci_revision rev;
};
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
@@ -248,6 +283,36 @@ static inline void ci_role_stop(struct ci_hdrc *ci)
}
/**
+ * hw_read_id_reg: reads from a identification register
+ * @ci: the controller
+ * @offset: offset from the beginning of identification registers region
+ * @mask: bitfield mask
+ *
+ * This function returns register contents
+ */
+static inline u32 hw_read_id_reg(struct ci_hdrc *ci, u32 offset, u32 mask)
+{
+ return ioread32(ci->hw_bank.abs + offset) & mask;
+}
+
+/**
+ * hw_write_id_reg: writes to a identification register
+ * @ci: the controller
+ * @offset: offset from the beginning of identification registers region
+ * @mask: bitfield mask
+ * @data: new value
+ */
+static inline void hw_write_id_reg(struct ci_hdrc *ci, u32 offset,
+ u32 mask, u32 data)
+{
+ if (~mask)
+ data = (ioread32(ci->hw_bank.abs + offset) & ~mask)
+ | (data & mask);
+
+ iowrite32(data, ci->hw_bank.abs + offset);
+}
+
+/**
* hw_read: reads from a hw register
* @ci: the controller
* @reg: register index
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index 0f05de7c6b6c..389f0e034259 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -23,22 +23,40 @@
#include "ci.h"
#include "ci_hdrc_imx.h"
-#define CI_HDRC_IMX_IMX28_WRITE_FIX BIT(0)
-
struct ci_hdrc_imx_platform_flag {
unsigned int flags;
+ bool runtime_pm;
};
static const struct ci_hdrc_imx_platform_flag imx27_usb_data = {
};
static const struct ci_hdrc_imx_platform_flag imx28_usb_data = {
- .flags = CI_HDRC_IMX_IMX28_WRITE_FIX,
+ .flags = CI_HDRC_IMX28_WRITE_FIX |
+ CI_HDRC_TURN_VBUS_EARLY_ON,
+};
+
+static const struct ci_hdrc_imx_platform_flag imx6q_usb_data = {
+ .flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
+ CI_HDRC_TURN_VBUS_EARLY_ON,
+};
+
+static const struct ci_hdrc_imx_platform_flag imx6sl_usb_data = {
+ .flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
+ CI_HDRC_TURN_VBUS_EARLY_ON,
+};
+
+static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = {
+ .flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
+ CI_HDRC_TURN_VBUS_EARLY_ON,
};
static const struct of_device_id ci_hdrc_imx_dt_ids[] = {
{ .compatible = "fsl,imx28-usb", .data = &imx28_usb_data},
{ .compatible = "fsl,imx27-usb", .data = &imx27_usb_data},
+ { .compatible = "fsl,imx6q-usb", .data = &imx6q_usb_data},
+ { .compatible = "fsl,imx6sl-usb", .data = &imx6sl_usb_data},
+ { .compatible = "fsl,imx6sx-usb", .data = &imx6sl_usb_data},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids);
@@ -48,6 +66,8 @@ struct ci_hdrc_imx_data {
struct platform_device *ci_pdev;
struct clk *clk;
struct imx_usbmisc_data *usbmisc_data;
+ bool supports_runtime_pm;
+ bool in_lpm;
};
/* Common functions shared by usbmisc drivers */
@@ -145,21 +165,18 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
}
pdata.usb_phy = data->phy;
-
- if (imx_platform_flag->flags & CI_HDRC_IMX_IMX28_WRITE_FIX)
- pdata.flags |= CI_HDRC_IMX28_WRITE_FIX;
+ pdata.flags |= imx_platform_flag->flags;
+ if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM)
+ data->supports_runtime_pm = true;
ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret)
goto err_clk;
- if (data->usbmisc_data) {
- ret = imx_usbmisc_init(data->usbmisc_data);
- if (ret) {
- dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n",
- ret);
- goto err_clk;
- }
+ ret = imx_usbmisc_init(data->usbmisc_data);
+ if (ret) {
+ dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n", ret);
+ goto err_clk;
}
data->ci_pdev = ci_hdrc_add_device(&pdev->dev,
@@ -173,19 +190,20 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
goto err_clk;
}
- if (data->usbmisc_data) {
- ret = imx_usbmisc_init_post(data->usbmisc_data);
- if (ret) {
- dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n",
- ret);
- goto disable_device;
- }
+ ret = imx_usbmisc_init_post(data->usbmisc_data);
+ if (ret) {
+ dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n", ret);
+ goto disable_device;
}
platform_set_drvdata(pdev, data);
- pm_runtime_no_callbacks(&pdev->dev);
- pm_runtime_enable(&pdev->dev);
+ if (data->supports_runtime_pm) {
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ }
+
+ device_set_wakeup_capable(&pdev->dev, true);
return 0;
@@ -200,14 +218,18 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev)
{
struct ci_hdrc_imx_data *data = platform_get_drvdata(pdev);
- pm_runtime_disable(&pdev->dev);
+ if (data->supports_runtime_pm) {
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ }
ci_hdrc_remove_device(data->ci_pdev);
clk_disable_unprepare(data->clk);
return 0;
}
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM
static int imx_controller_suspend(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
@@ -215,6 +237,7 @@ static int imx_controller_suspend(struct device *dev)
dev_dbg(dev, "at %s\n", __func__);
clk_disable_unprepare(data->clk);
+ data->in_lpm = true;
return 0;
}
@@ -222,25 +245,103 @@ static int imx_controller_suspend(struct device *dev)
static int imx_controller_resume(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
+ int ret = 0;
dev_dbg(dev, "at %s\n", __func__);
- return clk_prepare_enable(data->clk);
+ if (!data->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ ret = clk_prepare_enable(data->clk);
+ if (ret)
+ return ret;
+
+ data->in_lpm = false;
+
+ ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false);
+ if (ret) {
+ dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret);
+ goto clk_disable;
+ }
+
+ return 0;
+
+clk_disable:
+ clk_disable_unprepare(data->clk);
+ return ret;
}
+#ifdef CONFIG_PM_SLEEP
static int ci_hdrc_imx_suspend(struct device *dev)
{
+ int ret;
+
+ struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
+
+ if (data->in_lpm)
+ /* The core's suspend doesn't run */
+ return 0;
+
+ if (device_may_wakeup(dev)) {
+ ret = imx_usbmisc_set_wakeup(data->usbmisc_data, true);
+ if (ret) {
+ dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n",
+ ret);
+ return ret;
+ }
+ }
+
return imx_controller_suspend(dev);
}
static int ci_hdrc_imx_resume(struct device *dev)
{
- return imx_controller_resume(dev);
+ struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = imx_controller_resume(dev);
+ if (!ret && data->supports_runtime_pm) {
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ }
+
+ return ret;
}
#endif /* CONFIG_PM_SLEEP */
+static int ci_hdrc_imx_runtime_suspend(struct device *dev)
+{
+ struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ if (data->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ ret = imx_usbmisc_set_wakeup(data->usbmisc_data, true);
+ if (ret) {
+ dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ return imx_controller_suspend(dev);
+}
+
+static int ci_hdrc_imx_runtime_resume(struct device *dev)
+{
+ return imx_controller_resume(dev);
+}
+
+#endif /* CONFIG_PM */
+
static const struct dev_pm_ops ci_hdrc_imx_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ci_hdrc_imx_suspend, ci_hdrc_imx_resume)
+ SET_RUNTIME_PM_OPS(ci_hdrc_imx_runtime_suspend,
+ ci_hdrc_imx_runtime_resume, NULL)
};
static struct platform_driver ci_hdrc_imx_driver = {
.probe = ci_hdrc_imx_probe,
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h
index 4ed828f75a1e..635717e9354a 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.h
+++ b/drivers/usb/chipidea/ci_hdrc_imx.h
@@ -22,5 +22,6 @@ struct imx_usbmisc_data {
int imx_usbmisc_init(struct imx_usbmisc_data *);
int imx_usbmisc_init_post(struct imx_usbmisc_data *);
+int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *, bool);
#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */
diff --git a/drivers/usb/chipidea/ci_hdrc_pci.c b/drivers/usb/chipidea/ci_hdrc_pci.c
index 4df669437211..773d150512fa 100644
--- a/drivers/usb/chipidea/ci_hdrc_pci.c
+++ b/drivers/usb/chipidea/ci_hdrc_pci.c
@@ -16,10 +16,16 @@
#include <linux/interrupt.h>
#include <linux/usb/gadget.h>
#include <linux/usb/chipidea.h>
+#include <linux/usb/usb_phy_generic.h>
/* driver name */
#define UDC_DRIVER_NAME "ci_hdrc_pci"
+struct ci_hdrc_pci {
+ struct platform_device *ci;
+ struct platform_device *phy;
+};
+
/******************************************************************************
* PCI block
*****************************************************************************/
@@ -52,7 +58,7 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct ci_hdrc_platform_data *platdata = (void *)id->driver_data;
- struct platform_device *plat_ci;
+ struct ci_hdrc_pci *ci;
struct resource res[3];
int retval = 0, nres = 2;
@@ -61,6 +67,10 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev,
return -ENODEV;
}
+ ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL);
+ if (!ci)
+ return -ENOMEM;
+
retval = pcim_enable_device(pdev);
if (retval)
return retval;
@@ -73,6 +83,11 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev,
pci_set_master(pdev);
pci_try_set_mwi(pdev);
+ /* register a nop PHY */
+ ci->phy = usb_phy_generic_register();
+ if (!ci->phy)
+ return -ENOMEM;
+
memset(res, 0, sizeof(res));
res[0].start = pci_resource_start(pdev, 0);
res[0].end = pci_resource_end(pdev, 0);
@@ -80,13 +95,14 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev,
res[1].start = pdev->irq;
res[1].flags = IORESOURCE_IRQ;
- plat_ci = ci_hdrc_add_device(&pdev->dev, res, nres, platdata);
- if (IS_ERR(plat_ci)) {
+ ci->ci = ci_hdrc_add_device(&pdev->dev, res, nres, platdata);
+ if (IS_ERR(ci->ci)) {
dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n");
- return PTR_ERR(plat_ci);
+ usb_phy_generic_unregister(ci->phy);
+ return PTR_ERR(ci->ci);
}
- pci_set_drvdata(pdev, plat_ci);
+ pci_set_drvdata(pdev, ci);
return 0;
}
@@ -101,9 +117,10 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev,
*/
static void ci_hdrc_pci_remove(struct pci_dev *pdev)
{
- struct platform_device *plat_ci = pci_get_drvdata(pdev);
+ struct ci_hdrc_pci *ci = pci_get_drvdata(pdev);
- ci_hdrc_remove_device(plat_ci);
+ ci_hdrc_remove_device(ci->ci);
+ usb_phy_generic_unregister(ci->phy);
}
/**
diff --git a/drivers/usb/chipidea/ci_hdrc_zevio.c b/drivers/usb/chipidea/ci_hdrc_zevio.c
index d976fc1db73a..1264de505527 100644
--- a/drivers/usb/chipidea/ci_hdrc_zevio.c
+++ b/drivers/usb/chipidea/ci_hdrc_zevio.c
@@ -18,7 +18,7 @@
static struct ci_hdrc_platform_data ci_hdrc_zevio_platdata = {
.name = "ci_hdrc_zevio",
- .flags = CI_HDRC_REGS_SHARED,
+ .flags = CI_HDRC_REGS_SHARED | CI_HDRC_FORCE_FULLSPEED,
.capoffset = DEF_CAPOFFSET,
};
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index a57dc8866fc5..74fea4fa41b1 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -137,6 +137,22 @@ static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm)
return 0;
}
+static enum ci_revision ci_get_revision(struct ci_hdrc *ci)
+{
+ int ver = hw_read_id_reg(ci, ID_ID, VERSION) >> __ffs(VERSION);
+ enum ci_revision rev = CI_REVISION_UNKNOWN;
+
+ if (ver == 0x2) {
+ rev = hw_read_id_reg(ci, ID_ID, REVISION)
+ >> __ffs(REVISION);
+ rev += CI_REVISION_20;
+ } else if (ver == 0x0) {
+ rev = CI_REVISION_1X;
+ }
+
+ return rev;
+}
+
/**
* hw_read_intr_enable: returns interrupt enable register
*
@@ -251,8 +267,11 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
/* Clear all interrupts status bits*/
hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff);
- dev_dbg(ci->dev, "ChipIdea HDRC found, lpm: %d; cap: %p op: %p\n",
- ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op);
+ ci->rev = ci_get_revision(ci);
+
+ dev_dbg(ci->dev,
+ "ChipIdea HDRC found, revision: %d, lpm: %d; cap: %p op: %p\n",
+ ci->rev, ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op);
/* setup lock mode ? */
@@ -491,6 +510,13 @@ static irqreturn_t ci_irq(int irq, void *data)
irqreturn_t ret = IRQ_NONE;
u32 otgsc = 0;
+ if (ci->in_lpm) {
+ disable_irq_nosync(irq);
+ ci->wakeup_int = true;
+ pm_runtime_get(ci->dev);
+ return IRQ_HANDLED;
+ }
+
if (ci->is_otg) {
otgsc = hw_read_otgsc(ci, ~0);
if (ci_otg_is_fsm_mode(ci)) {
@@ -642,8 +668,12 @@ static void ci_get_otg_capable(struct ci_hdrc *ci)
ci->is_otg = (hw_read(ci, CAP_DCCPARAMS,
DCCPARAMS_DC | DCCPARAMS_HC)
== (DCCPARAMS_DC | DCCPARAMS_HC));
- if (ci->is_otg)
+ if (ci->is_otg) {
dev_dbg(ci->dev, "It is OTG capable controller\n");
+ /* Disable and clear all OTG irq */
+ hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
+ OTGSC_INT_STATUS_BITS);
+ }
}
static int ci_hdrc_probe(struct platform_device *pdev)
@@ -673,6 +703,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ci->platdata = dev_get_platdata(dev);
ci->imx28_write_fix = !!(ci->platdata->flags &
CI_HDRC_IMX28_WRITE_FIX);
+ ci->supports_runtime_pm = !!(ci->platdata->flags &
+ CI_HDRC_SUPPORTS_RUNTIME_PM);
ret = hw_device_init(ci, base);
if (ret < 0) {
@@ -740,9 +772,6 @@ static int ci_hdrc_probe(struct platform_device *pdev)
}
if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) {
- /* Disable and clear all OTG irq */
- hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
- OTGSC_INT_STATUS_BITS);
ret = ci_hdrc_otg_init(ci);
if (ret) {
dev_err(dev, "init otg fails, ret = %d\n", ret);
@@ -769,11 +798,11 @@ static int ci_hdrc_probe(struct platform_device *pdev)
: CI_ROLE_GADGET;
}
- /* only update vbus status for peripheral */
- if (ci->role == CI_ROLE_GADGET)
- ci_handle_vbus_change(ci);
-
if (!ci_otg_is_fsm_mode(ci)) {
+ /* only update vbus status for peripheral */
+ if (ci->role == CI_ROLE_GADGET)
+ ci_handle_vbus_change(ci);
+
ret = ci_role_start(ci, ci->role);
if (ret) {
dev_err(dev, "can't start %s role\n",
@@ -788,9 +817,19 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ret)
goto stop;
+ if (ci->supports_runtime_pm) {
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
+ pm_runtime_mark_last_busy(ci->dev);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ }
+
if (ci_otg_is_fsm_mode(ci))
ci_hdrc_otg_fsm_start(ci);
+ device_set_wakeup_capable(&pdev->dev, true);
+
ret = dbg_create_files(ci);
if (!ret)
return 0;
@@ -807,6 +846,12 @@ static int ci_hdrc_remove(struct platform_device *pdev)
{
struct ci_hdrc *ci = platform_get_drvdata(pdev);
+ if (ci->supports_runtime_pm) {
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ }
+
dbg_remove_files(ci);
ci_role_destroy(ci);
ci_hdrc_enter_lpm(ci, true);
@@ -815,13 +860,41 @@ static int ci_hdrc_remove(struct platform_device *pdev)
return 0;
}
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM
+/* Prepare wakeup by SRP before suspend */
+static void ci_otg_fsm_suspend_for_srp(struct ci_hdrc *ci)
+{
+ if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) &&
+ !hw_read_otgsc(ci, OTGSC_ID)) {
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP,
+ PORTSC_PP);
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_WKCN,
+ PORTSC_WKCN);
+ }
+}
+
+/* Handle SRP when wakeup by data pulse */
+static void ci_otg_fsm_wakeup_by_srp(struct ci_hdrc *ci)
+{
+ if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) &&
+ (ci->fsm.a_bus_drop == 1) && (ci->fsm.a_bus_req == 0)) {
+ if (!hw_read_otgsc(ci, OTGSC_ID)) {
+ ci->fsm.a_srp_det = 1;
+ ci->fsm.a_bus_drop = 0;
+ } else {
+ ci->fsm.id = 1;
+ }
+ ci_otg_queue_work(ci);
+ }
+}
+
static void ci_controller_suspend(struct ci_hdrc *ci)
{
+ disable_irq(ci->irq);
ci_hdrc_enter_lpm(ci, true);
-
- if (ci->usb_phy)
- usb_phy_set_suspend(ci->usb_phy, 1);
+ usb_phy_set_suspend(ci->usb_phy, 1);
+ ci->in_lpm = true;
+ enable_irq(ci->irq);
}
static int ci_controller_resume(struct device *dev)
@@ -830,23 +903,59 @@ static int ci_controller_resume(struct device *dev)
dev_dbg(dev, "at %s\n", __func__);
- ci_hdrc_enter_lpm(ci, false);
+ if (!ci->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+ ci_hdrc_enter_lpm(ci, false);
if (ci->usb_phy) {
usb_phy_set_suspend(ci->usb_phy, 0);
usb_phy_set_wakeup(ci->usb_phy, false);
hw_wait_phy_stable();
}
+ ci->in_lpm = false;
+ if (ci->wakeup_int) {
+ ci->wakeup_int = false;
+ pm_runtime_mark_last_busy(ci->dev);
+ pm_runtime_put_autosuspend(ci->dev);
+ enable_irq(ci->irq);
+ if (ci_otg_is_fsm_mode(ci))
+ ci_otg_fsm_wakeup_by_srp(ci);
+ }
+
return 0;
}
+#ifdef CONFIG_PM_SLEEP
static int ci_suspend(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
if (ci->wq)
flush_workqueue(ci->wq);
+ /*
+ * Controller needs to be active during suspend, otherwise the core
+ * may run resume when the parent is at suspend if other driver's
+ * suspend fails, it occurs before parent's suspend has not started,
+ * but the core suspend has finished.
+ */
+ if (ci->in_lpm)
+ pm_runtime_resume(dev);
+
+ if (ci->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ if (device_may_wakeup(dev)) {
+ if (ci_otg_is_fsm_mode(ci))
+ ci_otg_fsm_suspend_for_srp(ci);
+
+ usb_phy_set_wakeup(ci->usb_phy, true);
+ enable_irq_wake(ci->irq);
+ }
ci_controller_suspend(ci);
@@ -855,13 +964,57 @@ static int ci_suspend(struct device *dev)
static int ci_resume(struct device *dev)
{
- return ci_controller_resume(dev);
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+ int ret;
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(ci->irq);
+
+ ret = ci_controller_resume(dev);
+ if (ret)
+ return ret;
+
+ if (ci->supports_runtime_pm) {
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ }
+
+ return ret;
}
#endif /* CONFIG_PM_SLEEP */
+static int ci_runtime_suspend(struct device *dev)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "at %s\n", __func__);
+
+ if (ci->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ if (ci_otg_is_fsm_mode(ci))
+ ci_otg_fsm_suspend_for_srp(ci);
+
+ usb_phy_set_wakeup(ci->usb_phy, true);
+ ci_controller_suspend(ci);
+
+ return 0;
+}
+
+static int ci_runtime_resume(struct device *dev)
+{
+ return ci_controller_resume(dev);
+}
+
+#endif /* CONFIG_PM */
static const struct dev_pm_ops ci_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ci_suspend, ci_resume)
+ SET_RUNTIME_PM_OPS(ci_runtime_suspend, ci_runtime_resume, NULL)
};
+
static struct platform_driver ci_hdrc_driver = {
.probe = ci_hdrc_probe,
.remove = ci_hdrc_remove,
diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c
index 268e4236e84c..dfb05edcdb96 100644
--- a/drivers/usb/chipidea/debug.c
+++ b/drivers/usb/chipidea/debug.c
@@ -336,8 +336,8 @@ static int ci_registers_show(struct seq_file *s, void *unused)
struct ci_hdrc *ci = s->private;
u32 tmp_reg;
- if (!ci)
- return 0;
+ if (!ci || ci->in_lpm)
+ return -EPERM;
/* ------ Registers ----- */
tmp_reg = hw_read_intr_enable(ci);
diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
index 48731d0bab35..21fe1a314313 100644
--- a/drivers/usb/chipidea/host.c
+++ b/drivers/usb/chipidea/host.c
@@ -33,6 +33,7 @@
#include "host.h"
static struct hc_driver __read_mostly ci_ehci_hc_driver;
+static int (*orig_bus_suspend)(struct usb_hcd *hcd);
struct ehci_ci_priv {
struct regulator *reg_vbus;
@@ -43,11 +44,10 @@ static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct ehci_ci_priv *priv = (struct ehci_ci_priv *)ehci->priv;
struct device *dev = hcd->self.controller;
- struct ci_hdrc *ci = dev_get_drvdata(dev);
int ret = 0;
int port = HCS_N_PORTS(ehci->hcs_params);
- if (priv->reg_vbus && !ci_otg_is_fsm_mode(ci)) {
+ if (priv->reg_vbus) {
if (port > 1) {
dev_warn(dev,
"Not support multi-port regulator control\n");
@@ -113,12 +113,23 @@ static int host_start(struct ci_hdrc *ci)
priv = (struct ehci_ci_priv *)ehci->priv;
priv->reg_vbus = NULL;
- if (ci->platdata->reg_vbus)
- priv->reg_vbus = ci->platdata->reg_vbus;
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) {
+ if (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON) {
+ ret = regulator_enable(ci->platdata->reg_vbus);
+ if (ret) {
+ dev_err(ci->dev,
+ "Failed to enable vbus regulator, ret=%d\n",
+ ret);
+ goto put_hcd;
+ }
+ } else {
+ priv->reg_vbus = ci->platdata->reg_vbus;
+ }
+ }
ret = usb_add_hcd(hcd, 0, 0);
if (ret) {
- goto put_hcd;
+ goto disable_reg;
} else {
struct usb_otg *otg = &ci->otg;
@@ -133,8 +144,15 @@ static int host_start(struct ci_hdrc *ci)
if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING)
hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS);
+ if (ci->platdata->flags & CI_HDRC_FORCE_FULLSPEED)
+ hw_write(ci, OP_PORTSC, PORTSC_PFSC, PORTSC_PFSC);
+
return ret;
+disable_reg:
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) &&
+ (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON))
+ regulator_disable(ci->platdata->reg_vbus);
put_hcd:
usb_put_hcd(hcd);
@@ -148,6 +166,9 @@ static void host_stop(struct ci_hdrc *ci)
if (hcd) {
usb_remove_hcd(hcd);
usb_put_hcd(hcd);
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) &&
+ (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON))
+ regulator_disable(ci->platdata->reg_vbus);
}
}
@@ -158,6 +179,47 @@ void ci_hdrc_host_destroy(struct ci_hdrc *ci)
host_stop(ci);
}
+static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ int port;
+ u32 tmp;
+
+ int ret = orig_bus_suspend(hcd);
+
+ if (ret)
+ return ret;
+
+ port = HCS_N_PORTS(ehci->hcs_params);
+ while (port--) {
+ u32 __iomem *reg = &ehci->regs->port_status[port];
+ u32 portsc = ehci_readl(ehci, reg);
+
+ if (portsc & PORT_CONNECT) {
+ /*
+ * For chipidea, the resume signal will be ended
+ * automatically, so for remote wakeup case, the
+ * usbcmd.rs may not be set before the resume has
+ * ended if other resume paths consumes too much
+ * time (~24ms), in that case, the SOF will not
+ * send out within 3ms after resume ends, then the
+ * high speed device will enter full speed mode.
+ */
+
+ tmp = ehci_readl(ehci, &ehci->regs->command);
+ tmp |= CMD_RUN;
+ ehci_writel(ehci, tmp, &ehci->regs->command);
+ /*
+ * It needs a short delay between set RS bit and PHCD.
+ */
+ usleep_range(150, 200);
+ break;
+ }
+ }
+
+ return 0;
+}
+
int ci_hdrc_host_init(struct ci_hdrc *ci)
{
struct ci_role_driver *rdrv;
@@ -176,6 +238,8 @@ int ci_hdrc_host_init(struct ci_hdrc *ci)
ci->roles[CI_ROLE_HOST] = rdrv;
ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides);
+ orig_bus_suspend = ci_ehci_hc_driver.bus_suspend;
+ ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend;
return 0;
}
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index a048b08b9d4d..ad6c87a4653c 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -96,6 +96,7 @@ static void ci_otg_work(struct work_struct *work)
return;
}
+ pm_runtime_get_sync(ci->dev);
if (ci->id_event) {
ci->id_event = false;
ci_handle_id_switch(ci);
@@ -104,6 +105,7 @@ static void ci_otg_work(struct work_struct *work)
ci_handle_vbus_change(ci);
} else
dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
+ pm_runtime_put_sync(ci->dev);
enable_irq(ci->irq);
}
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
index 562e581f6765..083acf45ad5a 100644
--- a/drivers/usb/chipidea/otg_fsm.c
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -30,22 +30,6 @@
#include "otg.h"
#include "otg_fsm.h"
-static struct ci_otg_fsm_timer *otg_timer_initializer
-(struct ci_hdrc *ci, void (*function)(void *, unsigned long),
- unsigned long expires, unsigned long data)
-{
- struct ci_otg_fsm_timer *timer;
-
- timer = devm_kzalloc(ci->dev, sizeof(struct ci_otg_fsm_timer),
- GFP_KERNEL);
- if (!timer)
- return NULL;
- timer->function = function;
- timer->expires = expires;
- timer->data = data;
- return timer;
-}
-
/* Add for otg: interact with user space app */
static ssize_t
get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
@@ -204,229 +188,227 @@ static struct attribute_group inputs_attr_group = {
};
/*
+ * Keep this list in the same order as timers indexed
+ * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h
+ */
+static unsigned otg_timer_ms[] = {
+ TA_WAIT_VRISE,
+ TA_WAIT_VFALL,
+ TA_WAIT_BCON,
+ TA_AIDL_BDIS,
+ TB_ASE0_BRST,
+ TA_BIDL_ADIS,
+ TB_SE0_SRP,
+ TB_SRP_FAIL,
+ 0,
+ TB_DATA_PLS,
+ TB_SSEND_SRP,
+};
+
+/*
* Add timer to active timer list
*/
-static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t)
+static void ci_otg_add_timer(struct ci_hdrc *ci, enum otg_fsm_timer t)
{
- struct ci_otg_fsm_timer *tmp_timer;
- struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t];
- struct list_head *active_timers = &ci->fsm_timer->active_timers;
+ unsigned long flags, timer_sec, timer_nsec;
- if (t >= NUM_CI_OTG_FSM_TIMERS)
+ if (t >= NUM_OTG_FSM_TIMERS)
return;
- /*
- * Check if the timer is already in the active list,
- * if so update timer count
- */
- list_for_each_entry(tmp_timer, active_timers, list)
- if (tmp_timer == timer) {
- timer->count = timer->expires;
- return;
- }
-
- timer->count = timer->expires;
- list_add_tail(&timer->list, active_timers);
-
- /* Enable 1ms irq */
- if (!(hw_read_otgsc(ci, OTGSC_1MSIE)))
- hw_write_otgsc(ci, OTGSC_1MSIE, OTGSC_1MSIE);
+ spin_lock_irqsave(&ci->lock, flags);
+ timer_sec = otg_timer_ms[t] / MSEC_PER_SEC;
+ timer_nsec = (otg_timer_ms[t] % MSEC_PER_SEC) * NSEC_PER_MSEC;
+ ci->hr_timeouts[t] = ktime_add(ktime_get(),
+ ktime_set(timer_sec, timer_nsec));
+ ci->enabled_otg_timer_bits |= (1 << t);
+ if ((ci->next_otg_timer == NUM_OTG_FSM_TIMERS) ||
+ (ci->hr_timeouts[ci->next_otg_timer].tv64 >
+ ci->hr_timeouts[t].tv64)) {
+ ci->next_otg_timer = t;
+ hrtimer_start_range_ns(&ci->otg_fsm_hrtimer,
+ ci->hr_timeouts[t], NSEC_PER_MSEC,
+ HRTIMER_MODE_ABS);
+ }
+ spin_unlock_irqrestore(&ci->lock, flags);
}
/*
* Remove timer from active timer list
*/
-static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t)
+static void ci_otg_del_timer(struct ci_hdrc *ci, enum otg_fsm_timer t)
{
- struct ci_otg_fsm_timer *tmp_timer, *del_tmp;
- struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t];
- struct list_head *active_timers = &ci->fsm_timer->active_timers;
+ unsigned long flags, enabled_timer_bits;
+ enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS;
- if (t >= NUM_CI_OTG_FSM_TIMERS)
+ if ((t >= NUM_OTG_FSM_TIMERS) ||
+ !(ci->enabled_otg_timer_bits & (1 << t)))
return;
- list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list)
- if (tmp_timer == timer)
- list_del(&timer->list);
-
- /* Disable 1ms irq if there is no any active timer */
- if (list_empty(active_timers))
- hw_write_otgsc(ci, OTGSC_1MSIE, 0);
-}
-
-/*
- * Reduce timer count by 1, and find timeout conditions.
- * Called by otg 1ms timer interrupt
- */
-static inline int ci_otg_tick_timer(struct ci_hdrc *ci)
-{
- struct ci_otg_fsm_timer *tmp_timer, *del_tmp;
- struct list_head *active_timers = &ci->fsm_timer->active_timers;
- int expired = 0;
-
- list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) {
- tmp_timer->count--;
- /* check if timer expires */
- if (!tmp_timer->count) {
- list_del(&tmp_timer->list);
- tmp_timer->function(ci, tmp_timer->data);
- expired = 1;
+ spin_lock_irqsave(&ci->lock, flags);
+ ci->enabled_otg_timer_bits &= ~(1 << t);
+ if (ci->next_otg_timer == t) {
+ if (ci->enabled_otg_timer_bits == 0) {
+ /* No enabled timers after delete it */
+ hrtimer_cancel(&ci->otg_fsm_hrtimer);
+ ci->next_otg_timer = NUM_OTG_FSM_TIMERS;
+ } else {
+ /* Find the next timer */
+ enabled_timer_bits = ci->enabled_otg_timer_bits;
+ for_each_set_bit(cur_timer, &enabled_timer_bits,
+ NUM_OTG_FSM_TIMERS) {
+ if ((next_timer == NUM_OTG_FSM_TIMERS) ||
+ (ci->hr_timeouts[next_timer].tv64 <
+ ci->hr_timeouts[cur_timer].tv64))
+ next_timer = cur_timer;
+ }
}
}
-
- /* disable 1ms irq if there is no any timer active */
- if ((expired == 1) && list_empty(active_timers))
- hw_write_otgsc(ci, OTGSC_1MSIE, 0);
-
- return expired;
+ if (next_timer != NUM_OTG_FSM_TIMERS) {
+ ci->next_otg_timer = next_timer;
+ hrtimer_start_range_ns(&ci->otg_fsm_hrtimer,
+ ci->hr_timeouts[next_timer], NSEC_PER_MSEC,
+ HRTIMER_MODE_ABS);
+ }
+ spin_unlock_irqrestore(&ci->lock, flags);
}
-/* The timeout callback function to set time out bit */
-static void set_tmout(void *ptr, unsigned long indicator)
+/* OTG FSM timer handlers */
+static int a_wait_vrise_tmout(struct ci_hdrc *ci)
{
- *(int *)indicator = 1;
+ ci->fsm.a_wait_vrise_tmout = 1;
+ return 0;
}
-static void set_tmout_and_fsm(void *ptr, unsigned long indicator)
+static int a_wait_vfall_tmout(struct ci_hdrc *ci)
{
- struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
-
- set_tmout(ci, indicator);
-
- ci_otg_queue_work(ci);
+ ci->fsm.a_wait_vfall_tmout = 1;
+ return 0;
}
-static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator)
+static int a_wait_bcon_tmout(struct ci_hdrc *ci)
{
- struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
-
- set_tmout(ci, indicator);
- /* Disable port power */
- hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, 0);
- /* Clear existing DP irq */
- hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
- /* Enable data pulse irq */
- hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE);
- ci_otg_queue_work(ci);
+ ci->fsm.a_wait_bcon_tmout = 1;
+ return 0;
}
-static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator)
+static int a_aidl_bdis_tmout(struct ci_hdrc *ci)
{
- struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
-
- set_tmout(ci, indicator);
- if (!hw_read_otgsc(ci, OTGSC_BSV))
- ci->fsm.b_sess_vld = 0;
-
- ci_otg_queue_work(ci);
+ ci->fsm.a_aidl_bdis_tmout = 1;
+ return 0;
}
-static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator)
+static int b_ase0_brst_tmout(struct ci_hdrc *ci)
{
- struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
-
- set_tmout(ci, indicator);
-
- /* only vbus fall below B_sess_vld in b_idle state */
- if (ci->fsm.otg->state == OTG_STATE_B_IDLE)
- ci_otg_queue_work(ci);
+ ci->fsm.b_ase0_brst_tmout = 1;
+ return 0;
}
-static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator)
+static int a_bidl_adis_tmout(struct ci_hdrc *ci)
{
- struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+ ci->fsm.a_bidl_adis_tmout = 1;
+ return 0;
+}
- /* Check if A detached */
- if (!(hw_read_otgsc(ci, OTGSC_BSV))) {
- ci->fsm.b_sess_vld = 0;
- ci_otg_add_timer(ci, B_SSEND_SRP);
- ci_otg_queue_work(ci);
- }
+static int b_se0_srp_tmout(struct ci_hdrc *ci)
+{
+ ci->fsm.b_se0_srp = 1;
+ return 0;
}
-static void b_data_pulse_end(void *ptr, unsigned long indicator)
+static int b_srp_fail_tmout(struct ci_hdrc *ci)
{
- struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+ ci->fsm.b_srp_done = 1;
+ return 1;
+}
+static int b_data_pls_tmout(struct ci_hdrc *ci)
+{
ci->fsm.b_srp_done = 1;
ci->fsm.b_bus_req = 0;
if (ci->fsm.power_up)
ci->fsm.power_up = 0;
-
hw_write_otgsc(ci, OTGSC_HABA, 0);
+ pm_runtime_put(ci->dev);
+ return 0;
+}
- ci_otg_queue_work(ci);
+static int b_ssend_srp_tmout(struct ci_hdrc *ci)
+{
+ ci->fsm.b_ssend_srp = 1;
+ /* only vbus fall below B_sess_vld in b_idle state */
+ if (ci->fsm.otg->state == OTG_STATE_B_IDLE)
+ return 0;
+ else
+ return 1;
+}
+
+/*
+ * Keep this list in the same order as timers indexed
+ * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h
+ */
+static int (*otg_timer_handlers[])(struct ci_hdrc *) = {
+ a_wait_vrise_tmout, /* A_WAIT_VRISE */
+ a_wait_vfall_tmout, /* A_WAIT_VFALL */
+ a_wait_bcon_tmout, /* A_WAIT_BCON */
+ a_aidl_bdis_tmout, /* A_AIDL_BDIS */
+ b_ase0_brst_tmout, /* B_ASE0_BRST */
+ a_bidl_adis_tmout, /* A_BIDL_ADIS */
+ b_se0_srp_tmout, /* B_SE0_SRP */
+ b_srp_fail_tmout, /* B_SRP_FAIL */
+ NULL, /* A_WAIT_ENUM */
+ b_data_pls_tmout, /* B_DATA_PLS */
+ b_ssend_srp_tmout, /* B_SSEND_SRP */
+};
+
+/*
+ * Enable the next nearest enabled timer if have
+ */
+static enum hrtimer_restart ci_otg_hrtimer_func(struct hrtimer *t)
+{
+ struct ci_hdrc *ci = container_of(t, struct ci_hdrc, otg_fsm_hrtimer);
+ ktime_t now, *timeout;
+ unsigned long enabled_timer_bits;
+ unsigned long flags;
+ enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS;
+ int ret = -EINVAL;
+
+ spin_lock_irqsave(&ci->lock, flags);
+ enabled_timer_bits = ci->enabled_otg_timer_bits;
+ ci->next_otg_timer = NUM_OTG_FSM_TIMERS;
+
+ now = ktime_get();
+ for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) {
+ if (now.tv64 >= ci->hr_timeouts[cur_timer].tv64) {
+ ci->enabled_otg_timer_bits &= ~(1 << cur_timer);
+ if (otg_timer_handlers[cur_timer])
+ ret = otg_timer_handlers[cur_timer](ci);
+ } else {
+ if ((next_timer == NUM_OTG_FSM_TIMERS) ||
+ (ci->hr_timeouts[cur_timer].tv64 <
+ ci->hr_timeouts[next_timer].tv64))
+ next_timer = cur_timer;
+ }
+ }
+ /* Enable the next nearest timer */
+ if (next_timer < NUM_OTG_FSM_TIMERS) {
+ timeout = &ci->hr_timeouts[next_timer];
+ hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, *timeout,
+ NSEC_PER_MSEC, HRTIMER_MODE_ABS);
+ ci->next_otg_timer = next_timer;
+ }
+ spin_unlock_irqrestore(&ci->lock, flags);
+
+ if (!ret)
+ ci_otg_queue_work(ci);
+
+ return HRTIMER_NORESTART;
}
/* Initialize timers */
static int ci_otg_init_timers(struct ci_hdrc *ci)
{
- struct otg_fsm *fsm = &ci->fsm;
-
- /* FSM used timers */
- ci->fsm_timer->timer_list[A_WAIT_VRISE] =
- otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_VRISE,
- (unsigned long)&fsm->a_wait_vrise_tmout);
- if (ci->fsm_timer->timer_list[A_WAIT_VRISE] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[A_WAIT_VFALL] =
- otg_timer_initializer(ci, &a_wait_vfall_tmout_func,
- TA_WAIT_VFALL, (unsigned long)&fsm->a_wait_vfall_tmout);
- if (ci->fsm_timer->timer_list[A_WAIT_VFALL] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[A_WAIT_BCON] =
- otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_BCON,
- (unsigned long)&fsm->a_wait_bcon_tmout);
- if (ci->fsm_timer->timer_list[A_WAIT_BCON] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[A_AIDL_BDIS] =
- otg_timer_initializer(ci, &set_tmout_and_fsm, TA_AIDL_BDIS,
- (unsigned long)&fsm->a_aidl_bdis_tmout);
- if (ci->fsm_timer->timer_list[A_AIDL_BDIS] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[A_BIDL_ADIS] =
- otg_timer_initializer(ci, &set_tmout_and_fsm, TA_BIDL_ADIS,
- (unsigned long)&fsm->a_bidl_adis_tmout);
- if (ci->fsm_timer->timer_list[A_BIDL_ADIS] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[B_ASE0_BRST] =
- otg_timer_initializer(ci, &b_ase0_brst_tmout_func, TB_ASE0_BRST,
- (unsigned long)&fsm->b_ase0_brst_tmout);
- if (ci->fsm_timer->timer_list[B_ASE0_BRST] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[B_SE0_SRP] =
- otg_timer_initializer(ci, &set_tmout_and_fsm, TB_SE0_SRP,
- (unsigned long)&fsm->b_se0_srp);
- if (ci->fsm_timer->timer_list[B_SE0_SRP] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[B_SSEND_SRP] =
- otg_timer_initializer(ci, &b_ssend_srp_tmout_func, TB_SSEND_SRP,
- (unsigned long)&fsm->b_ssend_srp);
- if (ci->fsm_timer->timer_list[B_SSEND_SRP] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[B_SRP_FAIL] =
- otg_timer_initializer(ci, &set_tmout, TB_SRP_FAIL,
- (unsigned long)&fsm->b_srp_done);
- if (ci->fsm_timer->timer_list[B_SRP_FAIL] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[B_DATA_PLS] =
- otg_timer_initializer(ci, &b_data_pulse_end, TB_DATA_PLS, 0);
- if (ci->fsm_timer->timer_list[B_DATA_PLS] == NULL)
- return -ENOMEM;
-
- ci->fsm_timer->timer_list[B_SESS_VLD] = otg_timer_initializer(ci,
- &b_sess_vld_tmout_func, TB_SESS_VLD, 0);
- if (ci->fsm_timer->timer_list[B_SESS_VLD] == NULL)
- return -ENOMEM;
+ hrtimer_init(&ci->otg_fsm_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+ ci->otg_fsm_hrtimer.function = ci_otg_hrtimer_func;
return 0;
}
@@ -530,6 +512,7 @@ static void ci_otg_start_pulse(struct otg_fsm *fsm)
/* Hardware Assistant Data pulse */
hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP);
+ pm_runtime_get(ci->dev);
ci_otg_add_timer(ci, B_DATA_PLS);
}
@@ -585,6 +568,7 @@ int ci_otg_fsm_work(struct ci_hdrc *ci)
ci->fsm.otg->state < OTG_STATE_A_IDLE)
return 0;
+ pm_runtime_get_sync(ci->dev);
if (otg_statemachine(&ci->fsm)) {
if (ci->fsm.otg->state == OTG_STATE_A_IDLE) {
/*
@@ -596,8 +580,15 @@ int ci_otg_fsm_work(struct ci_hdrc *ci)
* a_idle to a_wait_vrise when power up
*/
if ((ci->fsm.id) || (ci->id_event) ||
- (ci->fsm.power_up))
+ (ci->fsm.power_up)) {
ci_otg_queue_work(ci);
+ } else {
+ /* Enable data pulse irq */
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS |
+ PORTSC_PP, 0);
+ hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
+ hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE);
+ }
if (ci->id_event)
ci->id_event = false;
} else if (ci->fsm.otg->state == OTG_STATE_B_IDLE) {
@@ -609,8 +600,13 @@ int ci_otg_fsm_work(struct ci_hdrc *ci)
*/
ci_otg_queue_work(ci);
}
+ } else if (ci->fsm.otg->state == OTG_STATE_A_HOST) {
+ pm_runtime_mark_last_busy(ci->dev);
+ pm_runtime_put_autosuspend(ci->dev);
+ return 0;
}
}
+ pm_runtime_put_sync(ci->dev);
return 0;
}
@@ -655,7 +651,6 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci)
fsm->a_conn = 0;
fsm->b_bus_req = 0;
ci_otg_queue_work(ci);
- ci_otg_add_timer(ci, B_SESS_VLD);
}
break;
case OTG_STATE_A_PERIPHERAL:
@@ -725,11 +720,7 @@ irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
fsm->id = (otgsc & OTGSC_ID) ? 1 : 0;
if (otg_int_src) {
- if (otg_int_src & OTGSC_1MSIS) {
- hw_write_otgsc(ci, OTGSC_1MSIS, OTGSC_1MSIS);
- retval = ci_otg_tick_timer(ci);
- return IRQ_HANDLED;
- } else if (otg_int_src & OTGSC_DPIS) {
+ if (otg_int_src & OTGSC_DPIS) {
hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
fsm->a_srp_det = 1;
fsm->a_bus_drop = 0;
@@ -793,17 +784,13 @@ int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
mutex_init(&ci->fsm.lock);
- ci->fsm_timer = devm_kzalloc(ci->dev,
- sizeof(struct ci_otg_fsm_timer_list), GFP_KERNEL);
- if (!ci->fsm_timer)
- return -ENOMEM;
-
- INIT_LIST_HEAD(&ci->fsm_timer->active_timers);
retval = ci_otg_init_timers(ci);
if (retval) {
dev_err(ci->dev, "Couldn't init OTG timers\n");
return retval;
}
+ ci->enabled_otg_timer_bits = 0;
+ ci->next_otg_timer = NUM_OTG_FSM_TIMERS;
retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group);
if (retval < 0) {
diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h
index 94c085f456a9..2689375ae5da 100644
--- a/drivers/usb/chipidea/otg_fsm.h
+++ b/drivers/usb/chipidea/otg_fsm.h
@@ -62,33 +62,6 @@
/* SSEND time before SRP */
#define TB_SSEND_SRP (1500) /* minimum 1.5 sec, section:5.1.2 */
-#define TB_SESS_VLD (1000)
-
-enum ci_otg_fsm_timer_index {
- /*
- * CI specific timers, start from the end
- * of standard and auxiliary OTG timers
- */
- B_DATA_PLS = NUM_OTG_FSM_TIMERS,
- B_SSEND_SRP,
- B_SESS_VLD,
-
- NUM_CI_OTG_FSM_TIMERS,
-};
-
-struct ci_otg_fsm_timer {
- unsigned long expires; /* Number of count increase to timeout */
- unsigned long count; /* Tick counter */
- void (*function)(void *, unsigned long); /* Timeout function */
- unsigned long data; /* Data passed to function */
- struct list_head list;
-};
-
-struct ci_otg_fsm_timer_list {
- struct ci_otg_fsm_timer *timer_list[NUM_CI_OTG_FSM_TIMERS];
- struct list_head active_timers;
-};
-
#ifdef CONFIG_USB_OTG_FSM
int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci);
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index 4bfb7ac0239f..764f668d45a9 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -86,10 +86,8 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma)
/* interrupt, error, port change, reset, sleep/suspend */
hw_write(ci, OP_USBINTR, ~0,
USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI);
- hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
} else {
hw_write(ci, OP_USBINTR, ~0, 0);
- hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
}
return 0;
}
@@ -522,6 +520,20 @@ static void free_pending_td(struct ci_hw_ep *hwep)
kfree(pending);
}
+static int reprime_dtd(struct ci_hdrc *ci, struct ci_hw_ep *hwep,
+ struct td_node *node)
+{
+ hwep->qh.ptr->td.next = node->dma;
+ hwep->qh.ptr->td.token &=
+ cpu_to_le32(~(TD_STATUS_HALTED | TD_STATUS_ACTIVE));
+
+ /* Synchronize before ep prime */
+ wmb();
+
+ return hw_ep_prime(ci, hwep->num, hwep->dir,
+ hwep->type == USB_ENDPOINT_XFER_CONTROL);
+}
+
/**
* _hardware_dequeue: handles a request at hardware level
* @gadget: gadget
@@ -535,6 +547,7 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
struct td_node *node, *tmpnode;
unsigned remaining_length;
unsigned actual = hwreq->req.length;
+ struct ci_hdrc *ci = hwep->ci;
if (hwreq->req.status != -EALREADY)
return -EINVAL;
@@ -544,6 +557,11 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) {
tmptoken = le32_to_cpu(node->ptr->token);
if ((TD_STATUS_ACTIVE & tmptoken) != 0) {
+ int n = hw_ep_bit(hwep->num, hwep->dir);
+
+ if (ci->rev == CI_REVISION_24)
+ if (!hw_read(ci, OP_ENDPTSTAT, BIT(n)))
+ reprime_dtd(ci, hwep, node);
hwreq->req.status = -EALREADY;
return -EBUSY;
}
@@ -1162,10 +1180,13 @@ static int ep_enable(struct usb_ep *ep,
/* only internal SW should enable ctrl endpts */
- hwep->ep.desc = desc;
-
- if (!list_empty(&hwep->qh.queue))
+ if (!list_empty(&hwep->qh.queue)) {
dev_warn(hwep->ci->dev, "enabling a non-empty endpoint!\n");
+ spin_unlock_irqrestore(hwep->lock, flags);
+ return -EBUSY;
+ }
+
+ hwep->ep.desc = desc;
hwep->dir = usb_endpoint_dir_in(desc) ? TX : RX;
hwep->num = usb_endpoint_num(desc);
@@ -1485,7 +1506,9 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
hw_device_reset(ci);
hw_device_state(ci, ci->ep0out->qh.dma);
usb_gadget_set_state(_gadget, USB_STATE_POWERED);
+ usb_udc_vbus_handler(_gadget, true);
} else {
+ usb_udc_vbus_handler(_gadget, false);
if (ci->driver)
ci->driver->disconnect(&ci->gadget);
hw_device_state(ci, 0);
@@ -1551,13 +1574,16 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on)
{
struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget);
- if (!ci->vbus_active)
- return -EOPNOTSUPP;
+ /* Data+ pullup controlled by OTG state machine in OTG fsm mode */
+ if (ci_otg_is_fsm_mode(ci))
+ return 0;
+ pm_runtime_get_sync(&ci->gadget.dev);
if (is_on)
hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
else
hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
+ pm_runtime_put_sync(&ci->gadget.dev);
return 0;
}
@@ -1687,6 +1713,7 @@ static int ci_udc_start(struct usb_gadget *gadget,
spin_lock_irqsave(&ci->lock, flags);
hw_device_reset(ci);
} else {
+ usb_udc_vbus_handler(&ci->gadget, false);
pm_runtime_put_sync(&ci->gadget.dev);
return retval;
}
diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c
index c3c6225b8acf..140945cb124f 100644
--- a/drivers/usb/chipidea/usbmisc_imx.c
+++ b/drivers/usb/chipidea/usbmisc_imx.c
@@ -11,7 +11,6 @@
#include <linux/module.h>
#include <linux/of_platform.h>
-#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/delay.h>
@@ -56,6 +55,19 @@
#define MX53_USB_PLL_DIV_24_MHZ 0x01
#define MX6_BM_OVER_CUR_DIS BIT(7)
+#define MX6_BM_WAKEUP_ENABLE BIT(10)
+#define MX6_BM_ID_WAKEUP BIT(16)
+#define MX6_BM_VBUS_WAKEUP BIT(17)
+#define MX6SX_BM_DPDM_WAKEUP_EN BIT(29)
+#define MX6_BM_WAKEUP_INTR BIT(31)
+#define MX6_USB_OTG1_PHY_CTRL 0x18
+/* For imx6dql, it is host-only controller, for later imx6, it is otg's */
+#define MX6_USB_OTG2_PHY_CTRL 0x1c
+#define MX6SX_USB_VBUS_WAKEUP_SOURCE(v) (v << 8)
+#define MX6SX_USB_VBUS_WAKEUP_SOURCE_VBUS MX6SX_USB_VBUS_WAKEUP_SOURCE(0)
+#define MX6SX_USB_VBUS_WAKEUP_SOURCE_AVALID MX6SX_USB_VBUS_WAKEUP_SOURCE(1)
+#define MX6SX_USB_VBUS_WAKEUP_SOURCE_BVALID MX6SX_USB_VBUS_WAKEUP_SOURCE(2)
+#define MX6SX_USB_VBUS_WAKEUP_SOURCE_SESS_END MX6SX_USB_VBUS_WAKEUP_SOURCE(3)
#define VF610_OVER_CUR_DIS BIT(7)
@@ -64,12 +76,13 @@ struct usbmisc_ops {
int (*init)(struct imx_usbmisc_data *data);
/* It's called once after adding a usb device */
int (*post)(struct imx_usbmisc_data *data);
+ /* It's called when we need to enable/disable usb wakeup */
+ int (*set_wakeup)(struct imx_usbmisc_data *data, bool enabled);
};
struct imx_usbmisc {
void __iomem *base;
spinlock_t lock;
- struct clk *clk;
const struct usbmisc_ops *ops;
};
@@ -204,6 +217,35 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data)
return 0;
}
+static int usbmisc_imx6q_set_wakeup
+ (struct imx_usbmisc_data *data, bool enabled)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 val;
+ u32 wakeup_setting = (MX6_BM_WAKEUP_ENABLE |
+ MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP);
+ int ret = 0;
+
+ if (data->index > 3)
+ return -EINVAL;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ val = readl(usbmisc->base + data->index * 4);
+ if (enabled) {
+ val |= wakeup_setting;
+ writel(val, usbmisc->base + data->index * 4);
+ } else {
+ if (val & MX6_BM_WAKEUP_INTR)
+ pr_debug("wakeup int at ci_hdrc.%d\n", data->index);
+ val &= ~wakeup_setting;
+ writel(val, usbmisc->base + data->index * 4);
+ }
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ return ret;
+}
+
static int usbmisc_imx6q_init(struct imx_usbmisc_data *data)
{
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
@@ -221,6 +263,36 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data)
spin_unlock_irqrestore(&usbmisc->lock, flags);
}
+ usbmisc_imx6q_set_wakeup(data, false);
+
+ return 0;
+}
+
+static int usbmisc_imx6sx_init(struct imx_usbmisc_data *data)
+{
+ void __iomem *reg = NULL;
+ unsigned long flags;
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ u32 val;
+
+ usbmisc_imx6q_init(data);
+
+ if (data->index == 0 || data->index == 1) {
+ reg = usbmisc->base + MX6_USB_OTG1_PHY_CTRL + data->index * 4;
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ /* Set vbus wakeup source as bvalid */
+ val = readl(reg);
+ writel(val | MX6SX_USB_VBUS_WAKEUP_SOURCE_BVALID, reg);
+ /*
+ * Disable dp/dm wakeup in device mode when vbus is
+ * not there.
+ */
+ val = readl(usbmisc->base + data->index * 4);
+ writel(val & ~MX6SX_BM_DPDM_WAKEUP_EN,
+ usbmisc->base + data->index * 4);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ }
+
return 0;
}
@@ -258,6 +330,7 @@ static const struct usbmisc_ops imx53_usbmisc_ops = {
};
static const struct usbmisc_ops imx6q_usbmisc_ops = {
+ .set_wakeup = usbmisc_imx6q_set_wakeup,
.init = usbmisc_imx6q_init,
};
@@ -265,10 +338,19 @@ static const struct usbmisc_ops vf610_usbmisc_ops = {
.init = usbmisc_vf610_init,
};
+static const struct usbmisc_ops imx6sx_usbmisc_ops = {
+ .set_wakeup = usbmisc_imx6q_set_wakeup,
+ .init = usbmisc_imx6sx_init,
+};
+
int imx_usbmisc_init(struct imx_usbmisc_data *data)
{
- struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ struct imx_usbmisc *usbmisc;
+
+ if (!data)
+ return 0;
+ usbmisc = dev_get_drvdata(data->dev);
if (!usbmisc->ops->init)
return 0;
return usbmisc->ops->init(data);
@@ -277,14 +359,32 @@ EXPORT_SYMBOL_GPL(imx_usbmisc_init);
int imx_usbmisc_init_post(struct imx_usbmisc_data *data)
{
- struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ struct imx_usbmisc *usbmisc;
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
if (!usbmisc->ops->post)
return 0;
return usbmisc->ops->post(data);
}
EXPORT_SYMBOL_GPL(imx_usbmisc_init_post);
+int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled)
+{
+ struct imx_usbmisc *usbmisc;
+
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
+ if (!usbmisc->ops->set_wakeup)
+ return 0;
+ return usbmisc->ops->set_wakeup(data, enabled);
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_set_wakeup);
+
static const struct of_device_id usbmisc_imx_dt_ids[] = {
{
.compatible = "fsl,imx25-usbmisc",
@@ -314,6 +414,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = {
.compatible = "fsl,vf610-usbmisc",
.data = &vf610_usbmisc_ops,
},
+ {
+ .compatible = "fsl,imx6sx-usbmisc",
+ .data = &imx6sx_usbmisc_ops,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids);
@@ -322,7 +426,6 @@ static int usbmisc_imx_probe(struct platform_device *pdev)
{
struct resource *res;
struct imx_usbmisc *data;
- int ret;
struct of_device_id *tmp_dev;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
@@ -336,20 +439,6 @@ static int usbmisc_imx_probe(struct platform_device *pdev)
if (IS_ERR(data->base))
return PTR_ERR(data->base);
- data->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(data->clk)) {
- dev_err(&pdev->dev,
- "failed to get clock, err=%ld\n", PTR_ERR(data->clk));
- return PTR_ERR(data->clk);
- }
-
- ret = clk_prepare_enable(data->clk);
- if (ret) {
- dev_err(&pdev->dev,
- "clk_prepare_enable failed, err=%d\n", ret);
- return ret;
- }
-
tmp_dev = (struct of_device_id *)
of_match_device(usbmisc_imx_dt_ids, &pdev->dev);
data->ops = (const struct usbmisc_ops *)tmp_dev->data;
@@ -360,8 +449,6 @@ static int usbmisc_imx_probe(struct platform_device *pdev)
static int usbmisc_imx_remove(struct platform_device *pdev)
{
- struct imx_usbmisc *usbmisc = dev_get_drvdata(&pdev->dev);
- clk_disable_unprepare(usbmisc->clk);
return 0;
}