aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/chipidea
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r--drivers/usb/chipidea/Kconfig2
-rw-r--r--drivers/usb/chipidea/Makefile4
-rw-r--r--drivers/usb/chipidea/bits.h13
-rw-r--r--drivers/usb/chipidea/ci.h114
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c55
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.h5
-rw-r--r--drivers/usb/chipidea/ci_hdrc_msm.c24
-rw-r--r--drivers/usb/chipidea/ci_hdrc_pci.c9
-rw-r--r--drivers/usb/chipidea/ci_hdrc_zevio.c72
-rw-r--r--drivers/usb/chipidea/core.c345
-rw-r--r--drivers/usb/chipidea/debug.c135
-rw-r--r--drivers/usb/chipidea/host.c33
-rw-r--r--drivers/usb/chipidea/otg.c48
-rw-r--r--drivers/usb/chipidea/otg.h25
-rw-r--r--drivers/usb/chipidea/otg_fsm.c842
-rw-r--r--drivers/usb/chipidea/otg_fsm.h129
-rw-r--r--drivers/usb/chipidea/udc.c494
-rw-r--r--drivers/usb/chipidea/usbmisc_imx.c114
18 files changed, 1999 insertions, 464 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig
index 4a851e15e58..77b47d82c9a 100644
--- a/drivers/usb/chipidea/Kconfig
+++ b/drivers/usb/chipidea/Kconfig
@@ -1,6 +1,6 @@
config USB_CHIPIDEA
tristate "ChipIdea Highspeed Dual Role Controller"
- depends on (USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)
+ depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA
help
Say Y here if your system has a dual role high speed USB
controller based on ChipIdea silicon IP. Currently, only the
diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile
index a99d980454a..2f099c7df7b 100644
--- a/drivers/usb/chipidea/Makefile
+++ b/drivers/usb/chipidea/Makefile
@@ -6,10 +6,12 @@ ci_hdrc-y := core.o otg.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o
+ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o
# Glue/Bridge layers go here
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),)
@@ -17,5 +19,5 @@ ifneq ($(CONFIG_PCI),)
endif
ifneq ($(CONFIG_OF),)
- obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_imx.o usbmisc_imx.o
+ obj-$(CONFIG_USB_CHIPIDEA) += usbmisc_imx.o ci_hdrc_imx.o
endif
diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h
index 464584c6cca..ca57e3dcd3d 100644
--- a/drivers/usb/chipidea/bits.h
+++ b/drivers/usb/chipidea/bits.h
@@ -44,17 +44,28 @@
#define DEVICEADDR_USBADR (0x7FUL << 25)
/* PORTSC */
+#define PORTSC_CCS BIT(0)
+#define PORTSC_CSC BIT(1)
+#define PORTSC_PEC BIT(3)
+#define PORTSC_OCC BIT(5)
#define PORTSC_FPR BIT(6)
#define PORTSC_SUSP BIT(7)
#define PORTSC_HSP BIT(9)
+#define PORTSC_PP BIT(12)
#define PORTSC_PTC (0x0FUL << 16)
+#define PORTSC_PHCD(d) ((d) ? BIT(22) : BIT(23))
/* PTS and PTW for non lpm version only */
+#define PORTSC_PFSC BIT(24)
#define PORTSC_PTS(d) \
(u32)((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0))
#define PORTSC_PTW BIT(28)
#define PORTSC_STS BIT(29)
+#define PORTSC_W1C_BITS \
+ (PORTSC_CSC | PORTSC_PEC | PORTSC_OCC)
+
/* DEVLC */
+#define DEVLC_PFSC BIT(23)
#define DEVLC_PSPD (0x03UL << 25)
#define DEVLC_PSPD_HS (0x02UL << 25)
#define DEVLC_PTW BIT(27)
@@ -69,6 +80,8 @@
/* OTGSC */
#define OTGSC_IDPU BIT(5)
+#define OTGSC_HADP BIT(6)
+#define OTGSC_HABA BIT(7)
#define OTGSC_ID BIT(8)
#define OTGSC_AVV BIT(9)
#define OTGSC_ASV BIT(10)
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 1c94fc5257f..9563cb56d56 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -17,6 +17,7 @@
#include <linux/irqreturn.h>
#include <linux/usb.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/otg-fsm.h>
/******************************************************************************
* DEFINE
@@ -26,6 +27,35 @@
#define ENDPT_MAX 32
/******************************************************************************
+ * REGISTERS
+ *****************************************************************************/
+/* register indices */
+enum ci_hw_regs {
+ CAP_CAPLENGTH,
+ CAP_HCCPARAMS,
+ CAP_DCCPARAMS,
+ CAP_TESTMODE,
+ CAP_LAST = CAP_TESTMODE,
+ OP_USBCMD,
+ OP_USBSTS,
+ OP_USBINTR,
+ OP_DEVICEADDR,
+ OP_ENDPTLISTADDR,
+ OP_PORTSC,
+ OP_DEVLC,
+ OP_OTGSC,
+ OP_USBMODE,
+ OP_ENDPTSETUPSTAT,
+ OP_ENDPTPRIME,
+ OP_ENDPTFLUSH,
+ OP_ENDPTSTAT,
+ OP_ENDPTCOMPLETE,
+ OP_ENDPTCTRL,
+ /* endptctrl1..15 follow */
+ OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2,
+};
+
+/******************************************************************************
* STRUCTURES
*****************************************************************************/
/**
@@ -98,7 +128,7 @@ struct hw_bank {
void __iomem *cap;
void __iomem *op;
size_t size;
- void __iomem **regmap;
+ void __iomem *regmap[OP_LAST + 1];
};
/**
@@ -110,6 +140,8 @@ struct hw_bank {
* @roles: array of supported roles for this controller
* @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
* @work: work for role changing
* @wq: workqueue thread
* @qh_pool: allocation pool for queue heads
@@ -135,6 +167,7 @@ struct hw_bank {
* @id_event: indicates there is an id event, and handled at ci_otg_work
* @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
*/
struct ci_hdrc {
struct device *dev;
@@ -144,6 +177,8 @@ struct ci_hdrc {
struct ci_role_driver *roles[CI_ROLE_END];
enum ci_role role;
bool is_otg;
+ struct otg_fsm fsm;
+ struct ci_otg_fsm_timer_list *fsm_timer;
struct work_struct work;
struct workqueue_struct *wq;
@@ -166,13 +201,12 @@ struct ci_hdrc {
struct ci_hdrc_platform_data *platdata;
int vbus_active;
- /* FIXME: some day, we'll not use global phy */
- bool global_phy;
struct usb_phy *transceiver;
struct usb_hcd *hcd;
struct dentry *debugfs;
bool id_event;
bool b_sess_valid_event;
+ bool imx28_write_fix;
};
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
@@ -209,38 +243,6 @@ static inline void ci_role_stop(struct ci_hdrc *ci)
ci->roles[role]->stop(ci);
}
-/******************************************************************************
- * REGISTERS
- *****************************************************************************/
-/* register size */
-#define REG_BITS (32)
-
-/* register indices */
-enum ci_hw_regs {
- CAP_CAPLENGTH,
- CAP_HCCPARAMS,
- CAP_DCCPARAMS,
- CAP_TESTMODE,
- CAP_LAST = CAP_TESTMODE,
- OP_USBCMD,
- OP_USBSTS,
- OP_USBINTR,
- OP_DEVICEADDR,
- OP_ENDPTLISTADDR,
- OP_PORTSC,
- OP_DEVLC,
- OP_OTGSC,
- OP_USBMODE,
- OP_ENDPTSETUPSTAT,
- OP_ENDPTPRIME,
- OP_ENDPTFLUSH,
- OP_ENDPTSTAT,
- OP_ENDPTCOMPLETE,
- OP_ENDPTCTRL,
- /* endptctrl1..15 follow */
- OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2,
-};
-
/**
* hw_read: reads from a hw register
* @reg: register index
@@ -253,6 +255,26 @@ static inline u32 hw_read(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask)
return ioread32(ci->hw_bank.regmap[reg]) & mask;
}
+#ifdef CONFIG_SOC_IMX28
+static inline void imx28_ci_writel(u32 val, volatile void __iomem *addr)
+{
+ __asm__ ("swp %0, %0, [%1]" : : "r"(val), "r"(addr));
+}
+#else
+static inline void imx28_ci_writel(u32 val, volatile void __iomem *addr)
+{
+}
+#endif
+
+static inline void __hw_write(struct ci_hdrc *ci, u32 val,
+ void __iomem *addr)
+{
+ if (ci->imx28_write_fix)
+ imx28_ci_writel(val, addr);
+ else
+ iowrite32(val, addr);
+}
+
/**
* hw_write: writes to a hw register
* @reg: register index
@@ -266,7 +288,7 @@ static inline void hw_write(struct ci_hdrc *ci, enum ci_hw_regs reg,
data = (ioread32(ci->hw_bank.regmap[reg]) & ~mask)
| (data & mask);
- iowrite32(data, ci->hw_bank.regmap[reg]);
+ __hw_write(ci, data, ci->hw_bank.regmap[reg]);
}
/**
@@ -281,7 +303,7 @@ static inline u32 hw_test_and_clear(struct ci_hdrc *ci, enum ci_hw_regs reg,
{
u32 val = ioread32(ci->hw_bank.regmap[reg]) & mask;
- iowrite32(val, ci->hw_bank.regmap[reg]);
+ __hw_write(ci, val, ci->hw_bank.regmap[reg]);
return val;
}
@@ -302,6 +324,24 @@ static inline u32 hw_test_and_write(struct ci_hdrc *ci, enum ci_hw_regs reg,
return (val & mask) >> __ffs(mask);
}
+/**
+ * ci_otg_is_fsm_mode: runtime check if otg controller
+ * is in otg fsm mode.
+ */
+static inline bool ci_otg_is_fsm_mode(struct ci_hdrc *ci)
+{
+#ifdef CONFIG_USB_OTG_FSM
+ return ci->is_otg && ci->roles[CI_ROLE_HOST] &&
+ ci->roles[CI_ROLE_GADGET];
+#else
+ return false;
+#endif
+}
+
+u32 hw_read_intr_enable(struct ci_hdrc *ci);
+
+u32 hw_read_intr_status(struct ci_hdrc *ci);
+
int hw_device_reset(struct ci_hdrc *ci, u32 mode);
int hw_port_test_set(struct ci_hdrc *ci, u8 mode);
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index 74d998d9b45..2e58f8dfd31 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -23,6 +23,26 @@
#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;
+};
+
+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,
+};
+
+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},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids);
+
struct ci_hdrc_imx_data {
struct usb_phy *phy;
struct platform_device *ci_pdev;
@@ -76,12 +96,15 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
{
struct ci_hdrc_imx_data *data;
struct ci_hdrc_platform_data pdata = {
- .name = "ci_hdrc_imx",
+ .name = dev_name(&pdev->dev),
.capoffset = DEF_CAPOFFSET,
.flags = CI_HDRC_REQUIRE_TRANSCEIVER |
CI_HDRC_DISABLE_STREAMING,
};
int ret;
+ const struct of_device_id *of_id =
+ of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev);
+ const struct ci_hdrc_imx_platform_flag *imx_platform_flag = of_id->data;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
@@ -108,23 +131,19 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
}
data->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "fsl,usbphy", 0);
- if (!IS_ERR(data->phy)) {
- ret = usb_phy_init(data->phy);
- if (ret) {
- dev_err(&pdev->dev, "unable to init phy: %d\n", ret);
- goto err_clk;
- }
- } else if (PTR_ERR(data->phy) == -EPROBE_DEFER) {
- ret = -EPROBE_DEFER;
+ if (IS_ERR(data->phy)) {
+ ret = PTR_ERR(data->phy);
goto err_clk;
}
pdata.phy = data->phy;
- if (!pdev->dev.dma_mask)
- pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
- if (!pdev->dev.coherent_dma_mask)
- pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+ if (imx_platform_flag->flags & CI_HDRC_IMX_IMX28_WRITE_FIX)
+ pdata.flags |= CI_HDRC_IMX28_WRITE_FIX;
+
+ 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);
@@ -175,21 +194,11 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev)
pm_runtime_disable(&pdev->dev);
ci_hdrc_remove_device(data->ci_pdev);
-
- if (data->phy)
- usb_phy_shutdown(data->phy);
-
clk_disable_unprepare(data->clk);
return 0;
}
-static const struct of_device_id ci_hdrc_imx_dt_ids[] = {
- { .compatible = "fsl,imx27-usb", },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids);
-
static struct platform_driver ci_hdrc_imx_driver = {
.probe = ci_hdrc_imx_probe,
.remove = ci_hdrc_imx_remove,
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h
index c7271590dd0..996ec93467b 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.h
+++ b/drivers/usb/chipidea/ci_hdrc_imx.h
@@ -9,6 +9,9 @@
* http://www.gnu.org/copyleft/gpl.html
*/
+#ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
+#define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
+
struct imx_usbmisc_data {
int index;
@@ -18,3 +21,5 @@ struct imx_usbmisc_data {
int imx_usbmisc_init(struct imx_usbmisc_data *);
int imx_usbmisc_init_post(struct imx_usbmisc_data *);
+
+#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */
diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c
index 2d51d852b47..d72b9d2de2c 100644
--- a/drivers/usb/chipidea/ci_hdrc_msm.c
+++ b/drivers/usb/chipidea/ci_hdrc_msm.c
@@ -47,6 +47,7 @@ static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)
static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = {
.name = "ci_hdrc_msm",
+ .capoffset = DEF_CAPOFFSET,
.flags = CI_HDRC_REGS_SHARED |
CI_HDRC_REQUIRE_TRANSCEIVER |
CI_HDRC_DISABLE_STREAMING,
@@ -57,9 +58,21 @@ static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = {
static int ci_hdrc_msm_probe(struct platform_device *pdev)
{
struct platform_device *plat_ci;
+ struct usb_phy *phy;
dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n");
+ /*
+ * OTG(PHY) driver takes care of PHY initialization, clock management,
+ * powering up VBUS, mapping of registers address space and power
+ * management.
+ */
+ phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ ci_hdrc_msm_platdata.phy = phy;
+
plat_ci = ci_hdrc_add_device(&pdev->dev,
pdev->resource, pdev->num_resources,
&ci_hdrc_msm_platdata);
@@ -86,10 +99,19 @@ static int ci_hdrc_msm_remove(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id msm_ci_dt_match[] = {
+ { .compatible = "qcom,ci-hdrc", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, msm_ci_dt_match);
+
static struct platform_driver ci_hdrc_msm_driver = {
.probe = ci_hdrc_msm_probe,
.remove = ci_hdrc_msm_remove,
- .driver = { .name = "msm_hsusb", },
+ .driver = {
+ .name = "msm_hsusb",
+ .of_match_table = msm_ci_dt_match,
+ },
};
module_platform_driver(ci_hdrc_msm_driver);
diff --git a/drivers/usb/chipidea/ci_hdrc_pci.c b/drivers/usb/chipidea/ci_hdrc_pci.c
index 042320a6c6c..241ae3444fd 100644
--- a/drivers/usb/chipidea/ci_hdrc_pci.c
+++ b/drivers/usb/chipidea/ci_hdrc_pci.c
@@ -112,7 +112,7 @@ static void ci_hdrc_pci_remove(struct pci_dev *pdev)
*
* Check "pci.h" for details
*/
-static DEFINE_PCI_DEVICE_TABLE(ci_hdrc_pci_id_table) = {
+static const struct pci_device_id ci_hdrc_pci_id_table[] = {
{
PCI_DEVICE(0x153F, 0x1004),
.driver_data = (kernel_ulong_t)&pci_platdata,
@@ -129,7 +129,12 @@ static DEFINE_PCI_DEVICE_TABLE(ci_hdrc_pci_id_table) = {
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0829),
.driver_data = (kernel_ulong_t)&penwell_pci_platdata,
},
- { 0, 0, 0, 0, 0, 0, 0 /* end: all zeroes */ }
+ {
+ /* Intel Clovertrail */
+ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe006),
+ .driver_data = (kernel_ulong_t)&penwell_pci_platdata,
+ },
+ { 0 } /* end: all zeroes */
};
MODULE_DEVICE_TABLE(pci, ci_hdrc_pci_id_table);
diff --git a/drivers/usb/chipidea/ci_hdrc_zevio.c b/drivers/usb/chipidea/ci_hdrc_zevio.c
new file mode 100644
index 00000000000..3bf6489ef5e
--- /dev/null
+++ b/drivers/usb/chipidea/ci_hdrc_zevio.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013 Daniel Tang <tangrs@tangrs.id.au>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ * Based off drivers/usb/chipidea/ci_hdrc_msm.c
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/chipidea.h>
+
+#include "ci.h"
+
+static struct ci_hdrc_platform_data ci_hdrc_zevio_platdata = {
+ .name = "ci_hdrc_zevio",
+ .flags = CI_HDRC_REGS_SHARED,
+ .capoffset = DEF_CAPOFFSET,
+};
+
+static int ci_hdrc_zevio_probe(struct platform_device *pdev)
+{
+ struct platform_device *ci_pdev;
+
+ dev_dbg(&pdev->dev, "ci_hdrc_zevio_probe\n");
+
+ ci_pdev = ci_hdrc_add_device(&pdev->dev,
+ pdev->resource, pdev->num_resources,
+ &ci_hdrc_zevio_platdata);
+
+ if (IS_ERR(ci_pdev)) {
+ dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n");
+ return PTR_ERR(ci_pdev);
+ }
+
+ platform_set_drvdata(pdev, ci_pdev);
+
+ return 0;
+}
+
+static int ci_hdrc_zevio_remove(struct platform_device *pdev)
+{
+ struct platform_device *ci_pdev = platform_get_drvdata(pdev);
+
+ ci_hdrc_remove_device(ci_pdev);
+
+ return 0;
+}
+
+static const struct of_device_id ci_hdrc_zevio_dt_ids[] = {
+ { .compatible = "lsi,zevio-usb", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver ci_hdrc_zevio_driver = {
+ .probe = ci_hdrc_zevio_probe,
+ .remove = ci_hdrc_zevio_remove,
+ .driver = {
+ .name = "zevio_usb",
+ .owner = THIS_MODULE,
+ .of_match_table = ci_hdrc_zevio_dt_ids,
+ },
+};
+
+MODULE_DEVICE_TABLE(of, ci_hdrc_zevio_dt_ids);
+module_platform_driver(ci_hdrc_zevio_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 94626409559..619d13e2999 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -23,7 +23,7 @@
* - BUS: bus glue code, bus abstraction layer
*
* Compile Options
- * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities
+ * - CONFIG_USB_CHIPIDEA_DEBUG: enable debug facilities
* - STALL_IN: non-empty bulk-in pipes cannot be halted
* if defined mass storage compliance succeeds but with warnings
* => case 4: Hi > Dn
@@ -42,10 +42,6 @@
* - Not Supported: 15 & 16 (ISO)
*
* TODO List
- * - OTG
- * - Interrupt Traffic
- * - GET_STATUS(device) - always reports 0
- * - Gadget API (majority of optional features)
* - Suspend & Remote Wakeup
*/
#include <linux/delay.h>
@@ -64,6 +60,7 @@
#include <linux/usb/otg.h>
#include <linux/usb/chipidea.h>
#include <linux/usb/of.h>
+#include <linux/of.h>
#include <linux/phy.h>
#include <linux/regulator/consumer.h>
@@ -73,63 +70,57 @@
#include "host.h"
#include "debug.h"
#include "otg.h"
+#include "otg_fsm.h"
/* Controller register map */
-static uintptr_t ci_regs_nolpm[] = {
- [CAP_CAPLENGTH] = 0x000UL,
- [CAP_HCCPARAMS] = 0x008UL,
- [CAP_DCCPARAMS] = 0x024UL,
- [CAP_TESTMODE] = 0x038UL,
- [OP_USBCMD] = 0x000UL,
- [OP_USBSTS] = 0x004UL,
- [OP_USBINTR] = 0x008UL,
- [OP_DEVICEADDR] = 0x014UL,
- [OP_ENDPTLISTADDR] = 0x018UL,
- [OP_PORTSC] = 0x044UL,
- [OP_DEVLC] = 0x084UL,
- [OP_OTGSC] = 0x064UL,
- [OP_USBMODE] = 0x068UL,
- [OP_ENDPTSETUPSTAT] = 0x06CUL,
- [OP_ENDPTPRIME] = 0x070UL,
- [OP_ENDPTFLUSH] = 0x074UL,
- [OP_ENDPTSTAT] = 0x078UL,
- [OP_ENDPTCOMPLETE] = 0x07CUL,
- [OP_ENDPTCTRL] = 0x080UL,
+static const u8 ci_regs_nolpm[] = {
+ [CAP_CAPLENGTH] = 0x00U,
+ [CAP_HCCPARAMS] = 0x08U,
+ [CAP_DCCPARAMS] = 0x24U,
+ [CAP_TESTMODE] = 0x38U,
+ [OP_USBCMD] = 0x00U,
+ [OP_USBSTS] = 0x04U,
+ [OP_USBINTR] = 0x08U,
+ [OP_DEVICEADDR] = 0x14U,
+ [OP_ENDPTLISTADDR] = 0x18U,
+ [OP_PORTSC] = 0x44U,
+ [OP_DEVLC] = 0x84U,
+ [OP_OTGSC] = 0x64U,
+ [OP_USBMODE] = 0x68U,
+ [OP_ENDPTSETUPSTAT] = 0x6CU,
+ [OP_ENDPTPRIME] = 0x70U,
+ [OP_ENDPTFLUSH] = 0x74U,
+ [OP_ENDPTSTAT] = 0x78U,
+ [OP_ENDPTCOMPLETE] = 0x7CU,
+ [OP_ENDPTCTRL] = 0x80U,
};
-static uintptr_t ci_regs_lpm[] = {
- [CAP_CAPLENGTH] = 0x000UL,
- [CAP_HCCPARAMS] = 0x008UL,
- [CAP_DCCPARAMS] = 0x024UL,
- [CAP_TESTMODE] = 0x0FCUL,
- [OP_USBCMD] = 0x000UL,
- [OP_USBSTS] = 0x004UL,
- [OP_USBINTR] = 0x008UL,
- [OP_DEVICEADDR] = 0x014UL,
- [OP_ENDPTLISTADDR] = 0x018UL,
- [OP_PORTSC] = 0x044UL,
- [OP_DEVLC] = 0x084UL,
- [OP_OTGSC] = 0x0C4UL,
- [OP_USBMODE] = 0x0C8UL,
- [OP_ENDPTSETUPSTAT] = 0x0D8UL,
- [OP_ENDPTPRIME] = 0x0DCUL,
- [OP_ENDPTFLUSH] = 0x0E0UL,
- [OP_ENDPTSTAT] = 0x0E4UL,
- [OP_ENDPTCOMPLETE] = 0x0E8UL,
- [OP_ENDPTCTRL] = 0x0ECUL,
+static const u8 ci_regs_lpm[] = {
+ [CAP_CAPLENGTH] = 0x00U,
+ [CAP_HCCPARAMS] = 0x08U,
+ [CAP_DCCPARAMS] = 0x24U,
+ [CAP_TESTMODE] = 0xFCU,
+ [OP_USBCMD] = 0x00U,
+ [OP_USBSTS] = 0x04U,
+ [OP_USBINTR] = 0x08U,
+ [OP_DEVICEADDR] = 0x14U,
+ [OP_ENDPTLISTADDR] = 0x18U,
+ [OP_PORTSC] = 0x44U,
+ [OP_DEVLC] = 0x84U,
+ [OP_OTGSC] = 0xC4U,
+ [OP_USBMODE] = 0xC8U,
+ [OP_ENDPTSETUPSTAT] = 0xD8U,
+ [OP_ENDPTPRIME] = 0xDCU,
+ [OP_ENDPTFLUSH] = 0xE0U,
+ [OP_ENDPTSTAT] = 0xE4U,
+ [OP_ENDPTCOMPLETE] = 0xE8U,
+ [OP_ENDPTCTRL] = 0xECU,
};
static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm)
{
int i;
- kfree(ci->hw_bank.regmap);
-
- ci->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *),
- GFP_KERNEL);
- if (!ci->hw_bank.regmap)
- return -ENOMEM;
-
for (i = 0; i < OP_ENDPTCTRL; i++)
ci->hw_bank.regmap[i] =
(i <= CAP_LAST ? ci->hw_bank.cap : ci->hw_bank.op) +
@@ -146,6 +137,26 @@ static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm)
}
/**
+ * hw_read_intr_enable: returns interrupt enable register
+ *
+ * This function returns register data
+ */
+u32 hw_read_intr_enable(struct ci_hdrc *ci)
+{
+ return hw_read(ci, OP_USBINTR, ~0);
+}
+
+/**
+ * hw_read_intr_status: returns interrupt status register
+ *
+ * This function returns register data
+ */
+u32 hw_read_intr_status(struct ci_hdrc *ci)
+{
+ return hw_read(ci, OP_USBSTS, ~0);
+}
+
+/**
* hw_port_test_set: writes port test mode (execute without interruption)
* @mode: new value
*
@@ -172,6 +183,26 @@ u8 hw_port_test_get(struct ci_hdrc *ci)
return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> __ffs(PORTSC_PTC);
}
+/* The PHY enters/leaves low power mode */
+static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable)
+{
+ enum ci_hw_regs reg = ci->hw_bank.lpm ? OP_DEVLC : OP_PORTSC;
+ bool lpm = !!(hw_read(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm)));
+
+ if (enable && !lpm) {
+ hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm),
+ PORTSC_PHCD(ci->hw_bank.lpm));
+ } else if (!enable && lpm) {
+ hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm),
+ 0);
+ /*
+ * the PHY needs some time (less
+ * than 1ms) to leave low power mode.
+ */
+ usleep_range(1000, 1100);
+ }
+}
+
static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
{
u32 reg;
@@ -187,7 +218,8 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
reg = hw_read(ci, CAP_HCCPARAMS, HCCPARAMS_LEN) >>
__ffs(HCCPARAMS_LEN);
ci->hw_bank.lpm = reg;
- hw_alloc_regmap(ci, !!reg);
+ if (reg)
+ hw_alloc_regmap(ci, !!reg);
ci->hw_bank.size = ci->hw_bank.op - ci->hw_bank.abs;
ci->hw_bank.size += OP_LAST;
ci->hw_bank.size /= sizeof(u32);
@@ -199,6 +231,8 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
if (ci->hw_ep_max > ENDPT_MAX)
return -ENODEV;
+ ci_hdrc_enter_lpm(ci, false);
+
/* Disable all interrupts bits */
hw_write(ci, OP_USBINTR, 0xffffffff, 0);
@@ -219,7 +253,7 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
static void hw_phymode_configure(struct ci_hdrc *ci)
{
- u32 portsc, lpm, sts;
+ u32 portsc, lpm, sts = 0;
switch (ci->platdata->phy_mode) {
case USBPHY_INTERFACE_MODE_UTMI:
@@ -249,14 +283,49 @@ static void hw_phymode_configure(struct ci_hdrc *ci)
if (ci->hw_bank.lpm) {
hw_write(ci, OP_DEVLC, DEVLC_PTS(7) | DEVLC_PTW, lpm);
- hw_write(ci, OP_DEVLC, DEVLC_STS, sts);
+ if (sts)
+ hw_write(ci, OP_DEVLC, DEVLC_STS, DEVLC_STS);
} else {
hw_write(ci, OP_PORTSC, PORTSC_PTS(7) | PORTSC_PTW, portsc);
- hw_write(ci, OP_PORTSC, PORTSC_STS, sts);
+ if (sts)
+ hw_write(ci, OP_PORTSC, PORTSC_STS, PORTSC_STS);
}
}
/**
+ * ci_usb_phy_init: initialize phy according to different phy type
+ * @ci: the controller
+ *
+ * This function returns an error code if usb_phy_init has failed
+ */
+static int ci_usb_phy_init(struct ci_hdrc *ci)
+{
+ int ret;
+
+ switch (ci->platdata->phy_mode) {
+ case USBPHY_INTERFACE_MODE_UTMI:
+ case USBPHY_INTERFACE_MODE_UTMIW:
+ case USBPHY_INTERFACE_MODE_HSIC:
+ ret = usb_phy_init(ci->transceiver);
+ if (ret)
+ return ret;
+ hw_phymode_configure(ci);
+ break;
+ case USBPHY_INTERFACE_MODE_ULPI:
+ case USBPHY_INTERFACE_MODE_SERIAL:
+ hw_phymode_configure(ci);
+ ret = usb_phy_init(ci->transceiver);
+ if (ret)
+ return ret;
+ break;
+ default:
+ ret = usb_phy_init(ci->transceiver);
+ }
+
+ return ret;
+}
+
+/**
* hw_device_reset: resets chip (execute without interruption)
* @ci: the controller
*
@@ -279,6 +348,13 @@ int hw_device_reset(struct ci_hdrc *ci, u32 mode)
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) {
+ if (ci->hw_bank.lpm)
+ hw_write(ci, OP_DEVLC, DEVLC_PFSC, DEVLC_PFSC);
+ else
+ hw_write(ci, OP_PORTSC, PORTSC_PFSC, PORTSC_PFSC);
+ }
+
/* USBMODE should be configured step by step */
hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE);
hw_write(ci, OP_USBMODE, USBMODE_CM, mode);
@@ -332,8 +408,14 @@ static irqreturn_t ci_irq(int irq, void *data)
irqreturn_t ret = IRQ_NONE;
u32 otgsc = 0;
- if (ci->is_otg)
- otgsc = hw_read(ci, OP_OTGSC, ~0);
+ if (ci->is_otg) {
+ otgsc = hw_read_otgsc(ci, ~0);
+ if (ci_otg_is_fsm_mode(ci)) {
+ ret = ci_otg_fsm_irq(ci);
+ if (ret == IRQ_HANDLED)
+ return ret;
+ }
+ }
/*
* Handle id change interrupt, it indicates device/host function
@@ -341,9 +423,9 @@ static irqreturn_t ci_irq(int irq, void *data)
*/
if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) {
ci->id_event = true;
- ci_clear_otg_interrupt(ci, OTGSC_IDIS);
- disable_irq_nosync(ci->irq);
- queue_work(ci->wq, &ci->work);
+ /* Clear ID change irq status */
+ hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS);
+ ci_otg_queue_work(ci);
return IRQ_HANDLED;
}
@@ -353,9 +435,9 @@ static irqreturn_t ci_irq(int irq, void *data)
*/
if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {
ci->b_sess_valid_event = true;
- ci_clear_otg_interrupt(ci, OTGSC_BSVIS);
- disable_irq_nosync(ci->irq);
- queue_work(ci->wq, &ci->work);
+ /* Clear BSV irq */
+ hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
+ ci_otg_queue_work(ci);
return IRQ_HANDLED;
}
@@ -369,18 +451,33 @@ static irqreturn_t ci_irq(int irq, void *data)
static int ci_get_platdata(struct device *dev,
struct ci_hdrc_platform_data *platdata)
{
- /* Get the vbus regulator */
- platdata->reg_vbus = devm_regulator_get(dev, "vbus");
- if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) {
- return -EPROBE_DEFER;
- } else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) {
- platdata->reg_vbus = NULL; /* no vbus regualator is needed */
- } else if (IS_ERR(platdata->reg_vbus)) {
- dev_err(dev, "Getting regulator error: %ld\n",
- PTR_ERR(platdata->reg_vbus));
- return PTR_ERR(platdata->reg_vbus);
+ if (!platdata->phy_mode)
+ platdata->phy_mode = of_usb_get_phy_mode(dev->of_node);
+
+ if (!platdata->dr_mode)
+ platdata->dr_mode = of_usb_get_dr_mode(dev->of_node);
+
+ if (platdata->dr_mode == USB_DR_MODE_UNKNOWN)
+ platdata->dr_mode = USB_DR_MODE_OTG;
+
+ if (platdata->dr_mode != USB_DR_MODE_PERIPHERAL) {
+ /* Get the vbus regulator */
+ platdata->reg_vbus = devm_regulator_get(dev, "vbus");
+ if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) {
+ /* no vbus regualator is needed */
+ platdata->reg_vbus = NULL;
+ } else if (IS_ERR(platdata->reg_vbus)) {
+ dev_err(dev, "Getting regulator error: %ld\n",
+ PTR_ERR(platdata->reg_vbus));
+ return PTR_ERR(platdata->reg_vbus);
+ }
}
+ if (of_usb_get_maximum_speed(dev->of_node) == USB_SPEED_FULL)
+ platdata->flags |= CI_HDRC_FORCE_FULLSPEED;
+
return 0;
}
@@ -458,11 +555,8 @@ 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");
- ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS);
- ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS);
- }
}
static int ci_hdrc_probe(struct platform_device *pdev)
@@ -473,9 +567,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
void __iomem *base;
int ret;
enum usb_dr_mode dr_mode;
- struct device_node *of_node = dev->of_node ?: dev->parent->of_node;
- if (!dev->platform_data) {
+ if (!dev_get_platdata(dev)) {
dev_err(dev, "platform data missing\n");
return -ENODEV;
}
@@ -492,11 +585,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
}
ci->dev = dev;
- ci->platdata = dev->platform_data;
- if (ci->platdata->phy)
- ci->transceiver = ci->platdata->phy;
- else
- ci->global_phy = true;
+ ci->platdata = dev_get_platdata(dev);
+ ci->imx28_write_fix = !!(ci->platdata->flags &
+ CI_HDRC_IMX28_WRITE_FIX);
ret = hw_device_init(ci, base);
if (ret < 0) {
@@ -504,27 +595,49 @@ static int ci_hdrc_probe(struct platform_device *pdev)
return -ENODEV;
}
+ if (ci->platdata->phy)
+ ci->transceiver = ci->platdata->phy;
+ else
+ ci->transceiver = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
+
+ if (IS_ERR(ci->transceiver)) {
+ ret = PTR_ERR(ci->transceiver);
+ /*
+ * if -ENXIO is returned, it means PHY layer wasn't
+ * enabled, so it makes no sense to return -EPROBE_DEFER
+ * in that case, since no PHY driver will ever probe.
+ */
+ if (ret == -ENXIO)
+ return ret;
+
+ dev_err(dev, "no usb2 phy configured\n");
+ return -EPROBE_DEFER;
+ }
+
+ ret = ci_usb_phy_init(ci);
+ if (ret) {
+ dev_err(dev, "unable to init phy: %d\n", ret);
+ return ret;
+ } else {
+ /*
+ * The delay to sync PHY's status, the maximum delay is
+ * 2ms since the otgsc uses 1ms timer to debounce the
+ * PHY's input
+ */
+ usleep_range(2000, 2500);
+ }
+
ci->hw_bank.phys = res->start;
ci->irq = platform_get_irq(pdev, 0);
if (ci->irq < 0) {
dev_err(dev, "missing IRQ\n");
- return -ENODEV;
+ ret = ci->irq;
+ goto deinit_phy;
}
ci_get_otg_capable(ci);
- if (!ci->platdata->phy_mode)
- ci->platdata->phy_mode = of_usb_get_phy_mode(of_node);
-
- hw_phymode_configure(ci);
-
- if (!ci->platdata->dr_mode)
- ci->platdata->dr_mode = of_usb_get_dr_mode(of_node);
-
- if (ci->platdata->dr_mode == USB_DR_MODE_UNKNOWN)
- ci->platdata->dr_mode = USB_DR_MODE_OTG;
-
dr_mode = ci->platdata->dr_mode;
/* initialize role(s) before the interrupt is requested */
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
@@ -541,10 +654,14 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) {
dev_err(dev, "no supported roles\n");
- return -ENODEV;
+ ret = -ENODEV;
+ goto deinit_phy;
}
if (ci->is_otg) {
+ /* 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);
@@ -554,13 +671,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
if (ci->is_otg) {
- /*
- * ID pin needs 1ms debouce time,
- * we delay 2ms for safe.
- */
- mdelay(2);
ci->role = ci_otg_role(ci);
- ci_enable_otg_interrupt(ci, OTGSC_IDIE);
+ /* Enable ID change irq */
+ hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
} else {
/*
* If the controller is not OTG capable, but support
@@ -575,10 +688,17 @@ static int ci_hdrc_probe(struct platform_device *pdev)
: CI_ROLE_GADGET;
}
- ret = ci_role_start(ci, ci->role);
- if (ret) {
- dev_err(dev, "can't start %s role\n", ci_role(ci)->name);
- goto stop;
+ /* only update vbus status for peripheral */
+ if (ci->role == CI_ROLE_GADGET)
+ ci_handle_vbus_change(ci);
+
+ if (!ci_otg_is_fsm_mode(ci)) {
+ ret = ci_role_start(ci, ci->role);
+ if (ret) {
+ dev_err(dev, "can't start %s role\n",
+ ci_role(ci)->name);
+ goto stop;
+ }
}
platform_set_drvdata(pdev, ci);
@@ -587,6 +707,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ret)
goto stop;
+ if (ci_otg_is_fsm_mode(ci))
+ ci_hdrc_otg_fsm_start(ci);
+
ret = dbg_create_files(ci);
if (!ret)
return 0;
@@ -594,6 +717,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
free_irq(ci->irq, ci);
stop:
ci_role_destroy(ci);
+deinit_phy:
+ usb_phy_shutdown(ci->transceiver);
return ret;
}
@@ -605,6 +730,9 @@ static int ci_hdrc_remove(struct platform_device *pdev)
dbg_remove_files(ci);
free_irq(ci->irq, ci);
ci_role_destroy(ci);
+ ci_hdrc_enter_lpm(ci, true);
+ usb_phy_shutdown(ci->transceiver);
+ kfree(ci->hw_bank.regmap);
return 0;
}
@@ -614,6 +742,7 @@ static struct platform_driver ci_hdrc_driver = {
.remove = ci_hdrc_remove,
.driver = {
.name = "ci_hdrc",
+ .owner = THIS_MODULE,
},
};
diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c
index 96d899aee47..7cccab6ff30 100644
--- a/drivers/usb/chipidea/debug.c
+++ b/drivers/usb/chipidea/debug.c
@@ -7,11 +7,15 @@
#include <linux/uaccess.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/phy.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/otg-fsm.h>
#include "ci.h"
#include "udc.h"
#include "bits.h"
#include "debug.h"
+#include "otg.h"
/**
* ci_device_show: prints information about device capabilities and status
@@ -204,6 +208,80 @@ static const struct file_operations ci_requests_fops = {
.release = single_release,
};
+int ci_otg_show(struct seq_file *s, void *unused)
+{
+ struct ci_hdrc *ci = s->private;
+ struct otg_fsm *fsm;
+
+ if (!ci || !ci_otg_is_fsm_mode(ci))
+ return 0;
+
+ fsm = &ci->fsm;
+
+ /* ------ State ----- */
+ seq_printf(s, "OTG state: %s\n\n",
+ usb_otg_state_string(ci->transceiver->state));
+
+ /* ------ State Machine Variables ----- */
+ seq_printf(s, "a_bus_drop: %d\n", fsm->a_bus_drop);
+
+ seq_printf(s, "a_bus_req: %d\n", fsm->a_bus_req);
+
+ seq_printf(s, "a_srp_det: %d\n", fsm->a_srp_det);
+
+ seq_printf(s, "a_vbus_vld: %d\n", fsm->a_vbus_vld);
+
+ seq_printf(s, "b_conn: %d\n", fsm->b_conn);
+
+ seq_printf(s, "adp_change: %d\n", fsm->adp_change);
+
+ seq_printf(s, "power_up: %d\n", fsm->power_up);
+
+ seq_printf(s, "a_bus_resume: %d\n", fsm->a_bus_resume);
+
+ seq_printf(s, "a_bus_suspend: %d\n", fsm->a_bus_suspend);
+
+ seq_printf(s, "a_conn: %d\n", fsm->a_conn);
+
+ seq_printf(s, "b_bus_req: %d\n", fsm->b_bus_req);
+
+ seq_printf(s, "b_bus_suspend: %d\n", fsm->b_bus_suspend);
+
+ seq_printf(s, "b_se0_srp: %d\n", fsm->b_se0_srp);
+
+ seq_printf(s, "b_ssend_srp: %d\n", fsm->b_ssend_srp);
+
+ seq_printf(s, "b_sess_vld: %d\n", fsm->b_sess_vld);
+
+ seq_printf(s, "b_srp_done: %d\n", fsm->b_srp_done);
+
+ seq_printf(s, "drv_vbus: %d\n", fsm->drv_vbus);
+
+ seq_printf(s, "loc_conn: %d\n", fsm->loc_conn);
+
+ seq_printf(s, "loc_sof: %d\n", fsm->loc_sof);
+
+ seq_printf(s, "adp_prb: %d\n", fsm->adp_prb);
+
+ seq_printf(s, "id: %d\n", fsm->id);
+
+ seq_printf(s, "protocol: %d\n", fsm->protocol);
+
+ return 0;
+}
+
+static int ci_otg_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ci_otg_show, inode->i_private);
+}
+
+static const struct file_operations ci_otg_fops = {
+ .open = ci_otg_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
static int ci_role_show(struct seq_file *s, void *data)
{
struct ci_hdrc *ci = s->private;
@@ -253,6 +331,50 @@ static const struct file_operations ci_role_fops = {
.release = single_release,
};
+int ci_registers_show(struct seq_file *s, void *unused)
+{
+ struct ci_hdrc *ci = s->private;
+ u32 tmp_reg;
+
+ if (!ci)
+ return 0;
+
+ /* ------ Registers ----- */
+ tmp_reg = hw_read_intr_enable(ci);
+ seq_printf(s, "USBINTR reg: %08x\n", tmp_reg);
+
+ tmp_reg = hw_read_intr_status(ci);
+ seq_printf(s, "USBSTS reg: %08x\n", tmp_reg);
+
+ tmp_reg = hw_read(ci, OP_USBMODE, ~0);
+ seq_printf(s, "USBMODE reg: %08x\n", tmp_reg);
+
+ tmp_reg = hw_read(ci, OP_USBCMD, ~0);
+ seq_printf(s, "USBCMD reg: %08x\n", tmp_reg);
+
+ tmp_reg = hw_read(ci, OP_PORTSC, ~0);
+ seq_printf(s, "PORTSC reg: %08x\n", tmp_reg);
+
+ if (ci->is_otg) {
+ tmp_reg = hw_read_otgsc(ci, ~0);
+ seq_printf(s, "OTGSC reg: %08x\n", tmp_reg);
+ }
+
+ return 0;
+}
+
+static int ci_registers_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ci_registers_show, inode->i_private);
+}
+
+static const struct file_operations ci_registers_fops = {
+ .open = ci_registers_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
/**
* dbg_create_files: initializes the attribute interface
* @ci: device
@@ -287,8 +409,21 @@ int dbg_create_files(struct ci_hdrc *ci)
if (!dent)
goto err;
+ if (ci_otg_is_fsm_mode(ci)) {
+ dent = debugfs_create_file("otg", S_IRUGO, ci->debugfs, ci,
+ &ci_otg_fops);
+ if (!dent)
+ goto err;
+ }
+
dent = debugfs_create_file("role", S_IRUGO | S_IWUSR, ci->debugfs, ci,
&ci_role_fops);
+ if (!dent)
+ goto err;
+
+ dent = debugfs_create_file("registers", S_IRUGO, ci->debugfs, ci,
+ &ci_registers_fops);
+
if (dent)
return 0;
err:
diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
index 6f96795dd20..a93d950e946 100644
--- a/drivers/usb/chipidea/host.c
+++ b/drivers/usb/chipidea/host.c
@@ -65,8 +65,13 @@ static int host_start(struct ci_hdrc *ci)
ehci->caps = ci->hw_bank.cap;
ehci->has_hostpc = ci->hw_bank.lpm;
ehci->has_tdi_phy_lpm = ci->hw_bank.lpm;
+ ehci->imx28_write_fix = ci->imx28_write_fix;
- if (ci->platdata->reg_vbus) {
+ /*
+ * vbus is always on if host is not in OTG FSM mode,
+ * otherwise should be controlled by OTG FSM
+ */
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) {
ret = regulator_enable(ci->platdata->reg_vbus);
if (ret) {
dev_err(ci->dev,
@@ -77,10 +82,17 @@ static int host_start(struct ci_hdrc *ci)
}
ret = usb_add_hcd(hcd, 0, 0);
- if (ret)
+ if (ret) {
goto disable_reg;
- else
+ } else {
+ struct usb_otg *otg = ci->transceiver->otg;
+
ci->hcd = hcd;
+ if (otg) {
+ otg->host = &hcd->self;
+ hcd->self.otg_port = 1;
+ }
+ }
if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING)
hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS);
@@ -88,7 +100,8 @@ static int host_start(struct ci_hdrc *ci)
return ret;
disable_reg:
- regulator_disable(ci->platdata->reg_vbus);
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci))
+ regulator_disable(ci->platdata->reg_vbus);
put_hcd:
usb_put_hcd(hcd);
@@ -100,16 +113,18 @@ static void host_stop(struct ci_hdrc *ci)
{
struct usb_hcd *hcd = ci->hcd;
- usb_remove_hcd(hcd);
- usb_put_hcd(hcd);
- if (ci->platdata->reg_vbus)
- regulator_disable(ci->platdata->reg_vbus);
+ if (hcd) {
+ usb_remove_hcd(hcd);
+ usb_put_hcd(hcd);
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci))
+ regulator_disable(ci->platdata->reg_vbus);
+ }
}
void ci_hdrc_host_destroy(struct ci_hdrc *ci)
{
- if (ci->role == CI_ROLE_HOST)
+ if (ci->role == CI_ROLE_HOST && ci->hcd)
host_stop(ci);
}
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index 39bd7ec8bf7..a048b08b9d4 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -11,8 +11,8 @@
*/
/*
- * This file mainly handles otgsc register, it may include OTG operation
- * in the future.
+ * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP
+ * are also included.
*/
#include <linux/usb/otg.h>
@@ -22,6 +22,26 @@
#include "ci.h"
#include "bits.h"
#include "otg.h"
+#include "otg_fsm.h"
+
+/**
+ * hw_read_otgsc returns otgsc register bits value.
+ * @mask: bitfield mask
+ */
+u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
+{
+ return hw_read(ci, OP_OTGSC, mask);
+}
+
+/**
+ * hw_write_otgsc updates target bits of OTGSC register.
+ * @mask: bitfield mask
+ * @data: to be written
+ */
+void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
+{
+ hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data);
+}
/**
* ci_otg_role - pick role based on ID pin state
@@ -29,8 +49,7 @@
*/
enum ci_role ci_otg_role(struct ci_hdrc *ci)
{
- u32 sts = hw_read(ci, OP_OTGSC, ~0);
- enum ci_role role = sts & OTGSC_ID
+ enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)
? CI_ROLE_GADGET
: CI_ROLE_HOST;
@@ -39,14 +58,10 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci)
void ci_handle_vbus_change(struct ci_hdrc *ci)
{
- u32 otgsc;
-
if (!ci->is_otg)
return;
- otgsc = hw_read(ci, OP_OTGSC, ~0);
-
- if (otgsc & OTGSC_BSV)
+ if (hw_read_otgsc(ci, OTGSC_BSV))
usb_gadget_vbus_connect(&ci->gadget);
else
usb_gadget_vbus_disconnect(&ci->gadget);
@@ -76,6 +91,11 @@ static void ci_otg_work(struct work_struct *work)
{
struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
+ if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
+ enable_irq(ci->irq);
+ return;
+ }
+
if (ci->id_event) {
ci->id_event = false;
ci_handle_id_switch(ci);
@@ -102,6 +122,9 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci)
return -ENODEV;
}
+ if (ci_otg_is_fsm_mode(ci))
+ return ci_hdrc_otg_fsm_init(ci);
+
return 0;
}
@@ -115,6 +138,9 @@ void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
flush_workqueue(ci->wq);
destroy_workqueue(ci->wq);
}
- ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS);
- ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS);
+ /* Disable all OTG irq and clear status */
+ hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
+ OTGSC_INT_STATUS_BITS);
+ if (ci_otg_is_fsm_mode(ci))
+ ci_hdrc_otg_fsm_remove(ci);
}
diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h
index 2d9f090733b..9ecb598e48f 100644
--- a/drivers/usb/chipidea/otg.h
+++ b/drivers/usb/chipidea/otg.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2013-2014 Freescale Semiconductor, Inc.
*
* Author: Peter Chen
*
@@ -11,25 +11,16 @@
#ifndef __DRIVERS_USB_CHIPIDEA_OTG_H
#define __DRIVERS_USB_CHIPIDEA_OTG_H
-static inline void ci_clear_otg_interrupt(struct ci_hdrc *ci, u32 bits)
-{
- /* Only clear request bits */
- hw_write(ci, OP_OTGSC, OTGSC_INT_STATUS_BITS, bits);
-}
-
-static inline void ci_enable_otg_interrupt(struct ci_hdrc *ci, u32 bits)
-{
- hw_write(ci, OP_OTGSC, bits, bits);
-}
-
-static inline void ci_disable_otg_interrupt(struct ci_hdrc *ci, u32 bits)
-{
- hw_write(ci, OP_OTGSC, bits, 0);
-}
-
+u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask);
+void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data);
int ci_hdrc_otg_init(struct ci_hdrc *ci);
void ci_hdrc_otg_destroy(struct ci_hdrc *ci);
enum ci_role ci_otg_role(struct ci_hdrc *ci);
void ci_handle_vbus_change(struct ci_hdrc *ci);
+static inline void ci_otg_queue_work(struct ci_hdrc *ci)
+{
+ disable_irq_nosync(ci->irq);
+ queue_work(ci->wq, &ci->work);
+}
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
new file mode 100644
index 00000000000..caaabc58021
--- /dev/null
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -0,0 +1,842 @@
+/*
+ * otg_fsm.c - ChipIdea USB IP core OTG FSM driver
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Jun Li
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*
+ * This file mainly handles OTG fsm, it includes OTG fsm operations
+ * for HNP and SRP.
+ *
+ * TODO List
+ * - ADP
+ * - OTG test device
+ */
+
+#include <linux/usb/otg.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/chipidea.h>
+#include <linux/regulator/consumer.h>
+
+#include "ci.h"
+#include "bits.h"
+#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)
+{
+ char *next;
+ unsigned size, t;
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ next = buf;
+ size = PAGE_SIZE;
+ t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_req);
+ size -= t;
+ next += t;
+
+ return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_a_bus_req(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ if (count > 2)
+ return -1;
+
+ mutex_lock(&ci->fsm.lock);
+ if (buf[0] == '0') {
+ ci->fsm.a_bus_req = 0;
+ } else if (buf[0] == '1') {
+ /* If a_bus_drop is TRUE, a_bus_req can't be set */
+ if (ci->fsm.a_bus_drop) {
+ mutex_unlock(&ci->fsm.lock);
+ return count;
+ }
+ ci->fsm.a_bus_req = 1;
+ }
+
+ ci_otg_queue_work(ci);
+ mutex_unlock(&ci->fsm.lock);
+
+ return count;
+}
+static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req, set_a_bus_req);
+
+static ssize_t
+get_a_bus_drop(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ char *next;
+ unsigned size, t;
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ next = buf;
+ size = PAGE_SIZE;
+ t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_drop);
+ size -= t;
+ next += t;
+
+ return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_a_bus_drop(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ if (count > 2)
+ return -1;
+
+ mutex_lock(&ci->fsm.lock);
+ if (buf[0] == '0') {
+ ci->fsm.a_bus_drop = 0;
+ } else if (buf[0] == '1') {
+ ci->fsm.a_bus_drop = 1;
+ ci->fsm.a_bus_req = 0;
+ }
+
+ ci_otg_queue_work(ci);
+ mutex_unlock(&ci->fsm.lock);
+
+ return count;
+}
+static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR, get_a_bus_drop,
+ set_a_bus_drop);
+
+static ssize_t
+get_b_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ char *next;
+ unsigned size, t;
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ next = buf;
+ size = PAGE_SIZE;
+ t = scnprintf(next, size, "%d\n", ci->fsm.b_bus_req);
+ size -= t;
+ next += t;
+
+ return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_b_bus_req(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ if (count > 2)
+ return -1;
+
+ mutex_lock(&ci->fsm.lock);
+ if (buf[0] == '0')
+ ci->fsm.b_bus_req = 0;
+ else if (buf[0] == '1')
+ ci->fsm.b_bus_req = 1;
+
+ ci_otg_queue_work(ci);
+ mutex_unlock(&ci->fsm.lock);
+
+ return count;
+}
+static DEVICE_ATTR(b_bus_req, S_IRUGO | S_IWUSR, get_b_bus_req, set_b_bus_req);
+
+static ssize_t
+set_a_clr_err(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ if (count > 2)
+ return -1;
+
+ mutex_lock(&ci->fsm.lock);
+ if (buf[0] == '1')
+ ci->fsm.a_clr_err = 1;
+
+ ci_otg_queue_work(ci);
+ mutex_unlock(&ci->fsm.lock);
+
+ return count;
+}
+static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err);
+
+static struct attribute *inputs_attrs[] = {
+ &dev_attr_a_bus_req.attr,
+ &dev_attr_a_bus_drop.attr,
+ &dev_attr_b_bus_req.attr,
+ &dev_attr_a_clr_err.attr,
+ NULL,
+};
+
+static struct attribute_group inputs_attr_group = {
+ .name = "inputs",
+ .attrs = inputs_attrs,
+};
+
+/*
+ * Add timer to active timer list
+ */
+static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index 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;
+
+ if (t >= NUM_CI_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);
+}
+
+/*
+ * Remove timer from active timer list
+ */
+static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index 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;
+
+ if (t >= NUM_CI_OTG_FSM_TIMERS)
+ 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;
+ }
+ }
+
+ /* 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;
+}
+
+/* The timeout callback function to set time out bit */
+static void set_tmout(void *ptr, unsigned long indicator)
+{
+ *(int *)indicator = 1;
+}
+
+static void set_tmout_and_fsm(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ set_tmout(ci, indicator);
+
+ ci_otg_queue_work(ci);
+}
+
+static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator)
+{
+ 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 exsiting 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);
+}
+
+static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator)
+{
+ 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);
+}
+
+static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator)
+{
+ 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->transceiver->state == OTG_STATE_B_IDLE)
+ ci_otg_queue_work(ci);
+}
+
+static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ /* 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 void b_data_pulse_end(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ 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);
+
+ ci_otg_queue_work(ci);
+}
+
+/* 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;
+
+ return 0;
+}
+
+/* -------------------------------------------------------------*/
+/* Operations that will be called from OTG Finite State Machine */
+/* -------------------------------------------------------------*/
+static void ci_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (t < NUM_OTG_FSM_TIMERS)
+ ci_otg_add_timer(ci, t);
+ return;
+}
+
+static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (t < NUM_OTG_FSM_TIMERS)
+ ci_otg_del_timer(ci, t);
+ return;
+}
+
+/*
+ * A-device drive vbus: turn on vbus regulator and enable port power
+ * Data pulse irq should be disabled while vbus is on.
+ */
+static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
+{
+ int ret;
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (on) {
+ /* Enable power power */
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP,
+ PORTSC_PP);
+ if (ci->platdata->reg_vbus) {
+ ret = regulator_enable(ci->platdata->reg_vbus);
+ if (ret) {
+ dev_err(ci->dev,
+ "Failed to enable vbus regulator, ret=%d\n",
+ ret);
+ return;
+ }
+ }
+ /* Disable data pulse irq */
+ hw_write_otgsc(ci, OTGSC_DPIE, 0);
+
+ fsm->a_srp_det = 0;
+ fsm->power_up = 0;
+ } else {
+ if (ci->platdata->reg_vbus)
+ regulator_disable(ci->platdata->reg_vbus);
+
+ fsm->a_bus_drop = 1;
+ fsm->a_bus_req = 0;
+ }
+}
+
+/*
+ * Control data line by Run Stop bit.
+ */
+static void ci_otg_loc_conn(struct otg_fsm *fsm, int on)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (on)
+ hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
+ else
+ hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
+}
+
+/*
+ * Generate SOF by host.
+ * This is controlled through suspend/resume the port.
+ * In host mode, controller will automatically send SOF.
+ * Suspend will block the data on the port.
+ */
+static void ci_otg_loc_sof(struct otg_fsm *fsm, int on)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (on)
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_FPR,
+ PORTSC_FPR);
+ else
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_SUSP,
+ PORTSC_SUSP);
+}
+
+/*
+ * Start SRP pulsing by data-line pulsing,
+ * no v-bus pulsing followed
+ */
+static void ci_otg_start_pulse(struct otg_fsm *fsm)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ /* Hardware Assistant Data pulse */
+ hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP);
+
+ ci_otg_add_timer(ci, B_DATA_PLS);
+}
+
+static int ci_otg_start_host(struct otg_fsm *fsm, int on)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ mutex_unlock(&fsm->lock);
+ if (on) {
+ ci_role_stop(ci);
+ ci_role_start(ci, CI_ROLE_HOST);
+ } else {
+ ci_role_stop(ci);
+ hw_device_reset(ci, USBMODE_CM_DC);
+ ci_role_start(ci, CI_ROLE_GADGET);
+ }
+ mutex_lock(&fsm->lock);
+ return 0;
+}
+
+static int ci_otg_start_gadget(struct otg_fsm *fsm, int on)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ mutex_unlock(&fsm->lock);
+ if (on)
+ usb_gadget_vbus_connect(&ci->gadget);
+ else
+ usb_gadget_vbus_disconnect(&ci->gadget);
+ mutex_lock(&fsm->lock);
+
+ return 0;
+}
+
+static struct otg_fsm_ops ci_otg_ops = {
+ .drv_vbus = ci_otg_drv_vbus,
+ .loc_conn = ci_otg_loc_conn,
+ .loc_sof = ci_otg_loc_sof,
+ .start_pulse = ci_otg_start_pulse,
+ .add_timer = ci_otg_fsm_add_timer,
+ .del_timer = ci_otg_fsm_del_timer,
+ .start_host = ci_otg_start_host,
+ .start_gadget = ci_otg_start_gadget,
+};
+
+int ci_otg_fsm_work(struct ci_hdrc *ci)
+{
+ /*
+ * Don't do fsm transition for B device
+ * when there is no gadget class driver
+ */
+ if (ci->fsm.id && !(ci->driver) &&
+ ci->transceiver->state < OTG_STATE_A_IDLE)
+ return 0;
+
+ if (otg_statemachine(&ci->fsm)) {
+ if (ci->transceiver->state == OTG_STATE_A_IDLE) {
+ /*
+ * Further state change for cases:
+ * a_idle to b_idle; or
+ * a_idle to a_wait_vrise due to ID change(1->0), so
+ * B-dev becomes A-dev can try to start new session
+ * consequently; or
+ * a_idle to a_wait_vrise when power up
+ */
+ if ((ci->fsm.id) || (ci->id_event) ||
+ (ci->fsm.power_up))
+ ci_otg_queue_work(ci);
+ if (ci->id_event)
+ ci->id_event = false;
+ } else if (ci->transceiver->state == OTG_STATE_B_IDLE) {
+ if (ci->fsm.b_sess_vld) {
+ ci->fsm.power_up = 0;
+ /*
+ * Further transite to b_periphearl state
+ * when register gadget driver with vbus on
+ */
+ ci_otg_queue_work(ci);
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * Update fsm variables in each state if catching expected interrupts,
+ * called by otg fsm isr.
+ */
+static void ci_otg_fsm_event(struct ci_hdrc *ci)
+{
+ u32 intr_sts, otg_bsess_vld, port_conn;
+ struct otg_fsm *fsm = &ci->fsm;
+
+ intr_sts = hw_read_intr_status(ci);
+ otg_bsess_vld = hw_read_otgsc(ci, OTGSC_BSV);
+ port_conn = hw_read(ci, OP_PORTSC, PORTSC_CCS);
+
+ switch (ci->transceiver->state) {
+ case OTG_STATE_A_WAIT_BCON:
+ if (port_conn) {
+ fsm->b_conn = 1;
+ fsm->a_bus_req = 1;
+ ci_otg_queue_work(ci);
+ }
+ break;
+ case OTG_STATE_B_IDLE:
+ if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) {
+ fsm->b_sess_vld = 1;
+ ci_otg_queue_work(ci);
+ }
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) {
+ fsm->a_bus_suspend = 1;
+ ci_otg_queue_work(ci);
+ } else if (intr_sts & USBi_PCI) {
+ if (fsm->a_bus_suspend == 1)
+ fsm->a_bus_suspend = 0;
+ }
+ break;
+ case OTG_STATE_B_HOST:
+ if ((intr_sts & USBi_PCI) && !port_conn) {
+ 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:
+ if (intr_sts & USBi_SLI) {
+ fsm->b_bus_suspend = 1;
+ /*
+ * Init a timer to know how long this suspend
+ * will contine, if time out, indicates B no longer
+ * wants to be host role
+ */
+ ci_otg_add_timer(ci, A_BIDL_ADIS);
+ }
+
+ if (intr_sts & USBi_URI)
+ ci_otg_del_timer(ci, A_BIDL_ADIS);
+
+ if (intr_sts & USBi_PCI) {
+ if (fsm->b_bus_suspend == 1) {
+ ci_otg_del_timer(ci, A_BIDL_ADIS);
+ fsm->b_bus_suspend = 0;
+ }
+ }
+ break;
+ case OTG_STATE_A_SUSPEND:
+ if ((intr_sts & USBi_PCI) && !port_conn) {
+ fsm->b_conn = 0;
+
+ /* if gadget driver is binded */
+ if (ci->driver) {
+ /* A device to be peripheral mode */
+ ci->gadget.is_a_peripheral = 1;
+ }
+ ci_otg_queue_work(ci);
+ }
+ break;
+ case OTG_STATE_A_HOST:
+ if ((intr_sts & USBi_PCI) && !port_conn) {
+ fsm->b_conn = 0;
+ ci_otg_queue_work(ci);
+ }
+ break;
+ case OTG_STATE_B_WAIT_ACON:
+ if ((intr_sts & USBi_PCI) && port_conn) {
+ fsm->a_conn = 1;
+ ci_otg_queue_work(ci);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * ci_otg_irq - otg fsm related irq handling
+ * and also update otg fsm variable by monitoring usb host and udc
+ * state change interrupts.
+ * @ci: ci_hdrc
+ */
+irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
+{
+ irqreturn_t retval = IRQ_NONE;
+ u32 otgsc, otg_int_src = 0;
+ struct otg_fsm *fsm = &ci->fsm;
+
+ otgsc = hw_read_otgsc(ci, ~0);
+ otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8);
+ 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) {
+ hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
+ fsm->a_srp_det = 1;
+ fsm->a_bus_drop = 0;
+ } else if (otg_int_src & OTGSC_IDIS) {
+ hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS);
+ if (fsm->id == 0) {
+ fsm->a_bus_drop = 0;
+ fsm->a_bus_req = 1;
+ ci->id_event = true;
+ }
+ } else if (otg_int_src & OTGSC_BSVIS) {
+ hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
+ if (otgsc & OTGSC_BSV) {
+ fsm->b_sess_vld = 1;
+ ci_otg_del_timer(ci, B_SSEND_SRP);
+ ci_otg_del_timer(ci, B_SRP_FAIL);
+ fsm->b_ssend_srp = 0;
+ } else {
+ fsm->b_sess_vld = 0;
+ if (fsm->id)
+ ci_otg_add_timer(ci, B_SSEND_SRP);
+ }
+ } else if (otg_int_src & OTGSC_AVVIS) {
+ hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS);
+ if (otgsc & OTGSC_AVV) {
+ fsm->a_vbus_vld = 1;
+ } else {
+ fsm->a_vbus_vld = 0;
+ fsm->b_conn = 0;
+ }
+ }
+ ci_otg_queue_work(ci);
+ return IRQ_HANDLED;
+ }
+
+ ci_otg_fsm_event(ci);
+
+ return retval;
+}
+
+void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
+{
+ ci_otg_queue_work(ci);
+}
+
+int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
+{
+ int retval = 0;
+ struct usb_otg *otg;
+
+ otg = devm_kzalloc(ci->dev,
+ sizeof(struct usb_otg), GFP_KERNEL);
+ if (!otg) {
+ dev_err(ci->dev,
+ "Failed to allocate usb_otg structure for ci hdrc otg!\n");
+ return -ENOMEM;
+ }
+
+ otg->phy = ci->transceiver;
+ otg->gadget = &ci->gadget;
+ ci->fsm.otg = otg;
+ ci->transceiver->otg = ci->fsm.otg;
+ ci->fsm.power_up = 1;
+ ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0;
+ ci->transceiver->state = OTG_STATE_UNDEFINED;
+ ci->fsm.ops = &ci_otg_ops;
+
+ 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) {
+ dev_err(ci->dev,
+ "Failed to allocate timer structure for ci hdrc otg!\n");
+ 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;
+ }
+
+ retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group);
+ if (retval < 0) {
+ dev_dbg(ci->dev,
+ "Can't register sysfs attr group: %d\n", retval);
+ return retval;
+ }
+
+ /* Enable A vbus valid irq */
+ hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE);
+
+ if (ci->fsm.id) {
+ ci->fsm.b_ssend_srp =
+ hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1;
+ ci->fsm.b_sess_vld =
+ hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0;
+ /* Enable BSV irq */
+ hw_write_otgsc(ci, OTGSC_BSVIE, OTGSC_BSVIE);
+ }
+
+ return 0;
+}
+
+void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
+{
+ sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group);
+}
diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h
new file mode 100644
index 00000000000..94c085f456a
--- /dev/null
+++ b/drivers/usb/chipidea/otg_fsm.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Jun Li
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __DRIVERS_USB_CHIPIDEA_OTG_FSM_H
+#define __DRIVERS_USB_CHIPIDEA_OTG_FSM_H
+
+#include <linux/usb/otg-fsm.h>
+
+/*
+ * A-DEVICE timing constants
+ */
+
+/* Wait for VBUS Rise */
+#define TA_WAIT_VRISE (100) /* a_wait_vrise: section 7.1.2
+ * a_wait_vrise_tmr: section 7.4.5.1
+ * TA_VBUS_RISE <= 100ms, section 4.4
+ * Table 4-1: Electrical Characteristics
+ * ->DC Electrical Timing
+ */
+/* Wait for VBUS Fall */
+#define TA_WAIT_VFALL (1000) /* a_wait_vfall: section 7.1.7
+ * a_wait_vfall_tmr: section: 7.4.5.2
+ */
+/* Wait for B-Connect */
+#define TA_WAIT_BCON (10000) /* a_wait_bcon: section 7.1.3
+ * TA_WAIT_BCON: should be between 1100
+ * and 30000 ms, section 5.5, Table 5-1
+ */
+/* A-Idle to B-Disconnect */
+#define TA_AIDL_BDIS (5000) /* a_suspend min 200 ms, section 5.2.1
+ * TA_AIDL_BDIS: section 5.5, Table 5-1
+ */
+/* B-Idle to A-Disconnect */
+#define TA_BIDL_ADIS (500) /* TA_BIDL_ADIS: section 5.2.1
+ * 500ms is used for B switch to host
+ * for safe
+ */
+
+/*
+ * B-device timing constants
+ */
+
+/* Data-Line Pulse Time*/
+#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms
+ * section:5.1.3
+ */
+/* SRP Fail Time */
+#define TB_SRP_FAIL (6000) /* b_srp_init,fail time 5~6s
+ * section:5.1.6
+ */
+/* A-SE0 to B-Reset */
+#define TB_ASE0_BRST (155) /* minimum 155 ms, section:5.3.1 */
+/* SE0 Time Before SRP */
+#define TB_SE0_SRP (1000) /* b_idle,minimum 1s, section:5.1.2 */
+/* 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);
+int ci_otg_fsm_work(struct ci_hdrc *ci);
+irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci);
+void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci);
+void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci);
+
+#else
+
+static inline int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
+{
+ return 0;
+}
+
+static inline int ci_otg_fsm_work(struct ci_hdrc *ci)
+{
+ return -ENXIO;
+}
+
+static inline irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
+{
+ return IRQ_NONE;
+}
+
+static inline void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
+{
+
+}
+
+static inline void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
+{
+
+}
+
+#endif
+
+#endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index 6b4c2f2eb94..b8125aa64ad 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -20,7 +20,7 @@
#include <linux/pm_runtime.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
-#include <linux/usb/otg.h>
+#include <linux/usb/otg-fsm.h>
#include <linux/usb/chipidea.h>
#include "ci.h"
@@ -28,6 +28,7 @@
#include "bits.h"
#include "debug.h"
#include "otg.h"
+#include "otg_fsm.h"
/* control endpoint description */
static const struct usb_endpoint_descriptor
@@ -106,7 +107,7 @@ static int hw_ep_flush(struct ci_hdrc *ci, int num, int dir)
do {
/* flush any pending transfer */
- hw_write(ci, OP_ENDPTFLUSH, BIT(n), BIT(n));
+ hw_write(ci, OP_ENDPTFLUSH, ~0, BIT(n));
while (hw_read(ci, OP_ENDPTFLUSH, BIT(n)))
cpu_relax();
} while (hw_read(ci, OP_ENDPTSTAT, BIT(n)));
@@ -179,19 +180,6 @@ static int hw_ep_get_halt(struct ci_hdrc *ci, int num, int dir)
}
/**
- * hw_test_and_clear_setup_status: test & clear setup status (execute without
- * interruption)
- * @n: endpoint number
- *
- * This function returns setup status
- */
-static int hw_test_and_clear_setup_status(struct ci_hdrc *ci, int n)
-{
- n = ep_to_bit(ci, n);
- return hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(n));
-}
-
-/**
* hw_ep_prime: primes endpoint (execute without interruption)
* @num: endpoint number
* @dir: endpoint direction
@@ -206,7 +194,7 @@ static int hw_ep_prime(struct ci_hdrc *ci, int num, int dir, int is_ctrl)
if (is_ctrl && dir == RX && hw_read(ci, OP_ENDPTSETUPSTAT, BIT(num)))
return -EAGAIN;
- hw_write(ci, OP_ENDPTPRIME, BIT(n), BIT(n));
+ hw_write(ci, OP_ENDPTPRIME, ~0, BIT(n));
while (hw_read(ci, OP_ENDPTPRIME, BIT(n)))
cpu_relax();
@@ -256,26 +244,6 @@ static int hw_port_is_high_speed(struct ci_hdrc *ci)
}
/**
- * hw_read_intr_enable: returns interrupt enable register
- *
- * This function returns register data
- */
-static u32 hw_read_intr_enable(struct ci_hdrc *ci)
-{
- return hw_read(ci, OP_USBINTR, ~0);
-}
-
-/**
- * hw_read_intr_status: returns interrupt status register
- *
- * This function returns register data
- */
-static u32 hw_read_intr_status(struct ci_hdrc *ci)
-{
- return hw_read(ci, OP_USBSTS, ~0);
-}
-
-/**
* hw_test_and_clear_complete: test & clear complete status (execute without
* interruption)
* @n: endpoint number
@@ -394,6 +362,14 @@ static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq,
node->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES));
node->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES);
node->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE);
+ if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) {
+ u32 mul = hwreq->req.length / hwep->ep.maxpacket;
+
+ if (hwreq->req.length == 0
+ || hwreq->req.length % hwep->ep.maxpacket)
+ mul++;
+ node->ptr->token |= mul << __ffs(TD_MULTO);
+ }
temp = (u32) (hwreq->req.dma + hwreq->req.actual);
if (length) {
@@ -516,10 +492,11 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
hwep->qh.ptr->td.token &=
cpu_to_le32(~(TD_STATUS_HALTED|TD_STATUS_ACTIVE));
- if (hwep->type == USB_ENDPOINT_XFER_ISOC) {
+ if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == RX) {
u32 mul = hwreq->req.length / hwep->ep.maxpacket;
- if (hwreq->req.length % hwep->ep.maxpacket)
+ if (hwreq->req.length == 0
+ || hwreq->req.length % hwep->ep.maxpacket)
mul++;
hwep->qh.ptr->cap |= mul << __ffs(QH_MULT);
}
@@ -686,9 +663,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
usb_ep_fifo_flush(&ci->ep0out->ep);
usb_ep_fifo_flush(&ci->ep0in->ep);
- if (ci->driver)
- ci->driver->disconnect(gadget);
-
/* make sure to disable all endpoints */
gadget_for_each_ep(ep, gadget) {
usb_ep_disable(ep);
@@ -718,6 +692,11 @@ __acquires(ci->lock)
int retval;
spin_unlock(&ci->lock);
+ if (ci->gadget.speed != USB_SPEED_UNKNOWN) {
+ if (ci->driver)
+ ci->driver->disconnect(&ci->gadget);
+ }
+
retval = _gadget_stop_activity(&ci->gadget);
if (retval)
goto done;
@@ -730,6 +709,8 @@ __acquires(ci->lock)
if (ci->status == NULL)
retval = -ENOMEM;
+ usb_gadget_set_state(&ci->gadget, USB_STATE_DEFAULT);
+
done:
spin_lock(&ci->lock);
@@ -844,7 +825,6 @@ __acquires(hwep->lock)
if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) {
/* Assume that device is bus powered for now. */
*(u16 *)req->buf = ci->remote_wakeup << 1;
- retval = 0;
} else if ((setup->bRequestType & USB_RECIP_MASK) \
== USB_RECIP_ENDPOINT) {
dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ?
@@ -886,6 +866,8 @@ isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req)
if (ci->setaddr) {
hw_usb_set_address(ci, ci->address);
ci->setaddr = false;
+ if (ci->address)
+ usb_gadget_set_state(&ci->gadget, USB_STATE_ADDRESS);
}
spin_lock_irqsave(&ci->lock, flags);
@@ -952,6 +934,164 @@ __acquires(hwep->lock)
}
/**
+ * isr_setup_packet_handler: setup packet handler
+ * @ci: UDC descriptor
+ *
+ * This function handles setup packet
+ */
+static void isr_setup_packet_handler(struct ci_hdrc *ci)
+__releases(ci->lock)
+__acquires(ci->lock)
+{
+ struct ci_hw_ep *hwep = &ci->ci_hw_ep[0];
+ struct usb_ctrlrequest req;
+ int type, num, dir, err = -EINVAL;
+ u8 tmode = 0;
+
+ /*
+ * Flush data and handshake transactions of previous
+ * setup packet.
+ */
+ _ep_nuke(ci->ep0out);
+ _ep_nuke(ci->ep0in);
+
+ /* read_setup_packet */
+ do {
+ hw_test_and_set_setup_guard(ci);
+ memcpy(&req, &hwep->qh.ptr->setup, sizeof(req));
+ } while (!hw_test_and_clear_setup_guard(ci));
+
+ type = req.bRequestType;
+
+ ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX;
+
+ switch (req.bRequest) {
+ case USB_REQ_CLEAR_FEATURE:
+ if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) &&
+ le16_to_cpu(req.wValue) ==
+ USB_ENDPOINT_HALT) {
+ if (req.wLength != 0)
+ break;
+ num = le16_to_cpu(req.wIndex);
+ dir = num & USB_ENDPOINT_DIR_MASK;
+ num &= USB_ENDPOINT_NUMBER_MASK;
+ if (dir) /* TX */
+ num += ci->hw_ep_max / 2;
+ if (!ci->ci_hw_ep[num].wedge) {
+ spin_unlock(&ci->lock);
+ err = usb_ep_clear_halt(
+ &ci->ci_hw_ep[num].ep);
+ spin_lock(&ci->lock);
+ if (err)
+ break;
+ }
+ err = isr_setup_status_phase(ci);
+ } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) &&
+ le16_to_cpu(req.wValue) ==
+ USB_DEVICE_REMOTE_WAKEUP) {
+ if (req.wLength != 0)
+ break;
+ ci->remote_wakeup = 0;
+ err = isr_setup_status_phase(ci);
+ } else {
+ goto delegate;
+ }
+ break;
+ case USB_REQ_GET_STATUS:
+ if (type != (USB_DIR_IN|USB_RECIP_DEVICE) &&
+ type != (USB_DIR_IN|USB_RECIP_ENDPOINT) &&
+ type != (USB_DIR_IN|USB_RECIP_INTERFACE))
+ goto delegate;
+ if (le16_to_cpu(req.wLength) != 2 ||
+ le16_to_cpu(req.wValue) != 0)
+ break;
+ err = isr_get_status_response(ci, &req);
+ break;
+ case USB_REQ_SET_ADDRESS:
+ if (type != (USB_DIR_OUT|USB_RECIP_DEVICE))
+ goto delegate;
+ if (le16_to_cpu(req.wLength) != 0 ||
+ le16_to_cpu(req.wIndex) != 0)
+ break;
+ ci->address = (u8)le16_to_cpu(req.wValue);
+ ci->setaddr = true;
+ err = isr_setup_status_phase(ci);
+ break;
+ case USB_REQ_SET_FEATURE:
+ if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) &&
+ le16_to_cpu(req.wValue) ==
+ USB_ENDPOINT_HALT) {
+ if (req.wLength != 0)
+ break;
+ num = le16_to_cpu(req.wIndex);
+ dir = num & USB_ENDPOINT_DIR_MASK;
+ num &= USB_ENDPOINT_NUMBER_MASK;
+ if (dir) /* TX */
+ num += ci->hw_ep_max / 2;
+
+ spin_unlock(&ci->lock);
+ err = usb_ep_set_halt(&ci->ci_hw_ep[num].ep);
+ spin_lock(&ci->lock);
+ if (!err)
+ isr_setup_status_phase(ci);
+ } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) {
+ if (req.wLength != 0)
+ break;
+ switch (le16_to_cpu(req.wValue)) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ ci->remote_wakeup = 1;
+ err = isr_setup_status_phase(ci);
+ break;
+ case USB_DEVICE_TEST_MODE:
+ tmode = le16_to_cpu(req.wIndex) >> 8;
+ switch (tmode) {
+ case TEST_J:
+ case TEST_K:
+ case TEST_SE0_NAK:
+ case TEST_PACKET:
+ case TEST_FORCE_EN:
+ ci->test_mode = tmode;
+ err = isr_setup_status_phase(
+ ci);
+ break;
+ default:
+ break;
+ }
+ break;
+ case USB_DEVICE_B_HNP_ENABLE:
+ if (ci_otg_is_fsm_mode(ci)) {
+ ci->gadget.b_hnp_enable = 1;
+ err = isr_setup_status_phase(
+ ci);
+ }
+ break;
+ default:
+ goto delegate;
+ }
+ } else {
+ goto delegate;
+ }
+ break;
+ default:
+delegate:
+ if (req.wLength == 0) /* no data phase */
+ ci->ep0_dir = TX;
+
+ spin_unlock(&ci->lock);
+ err = ci->driver->setup(&ci->gadget, &req);
+ spin_lock(&ci->lock);
+ break;
+ }
+
+ if (err < 0) {
+ spin_unlock(&ci->lock);
+ if (usb_ep_set_halt(&hwep->ep))
+ dev_err(ci->dev, "error: ep_set_halt\n");
+ spin_lock(&ci->lock);
+ }
+}
+
+/**
* isr_tr_complete_handler: transaction complete interrupt handler
* @ci: UDC descriptor
*
@@ -962,12 +1102,10 @@ __releases(ci->lock)
__acquires(ci->lock)
{
unsigned i;
- u8 tmode = 0;
+ int err;
for (i = 0; i < ci->hw_ep_max; i++) {
struct ci_hw_ep *hwep = &ci->ci_hw_ep[i];
- int type, num, dir, err = -EINVAL;
- struct usb_ctrlrequest req;
if (hwep->ep.desc == NULL)
continue; /* not configured */
@@ -987,148 +1125,10 @@ __acquires(ci->lock)
}
}
- if (hwep->type != USB_ENDPOINT_XFER_CONTROL ||
- !hw_test_and_clear_setup_status(ci, i))
- continue;
-
- if (i != 0) {
- dev_warn(ci->dev, "ctrl traffic at endpoint %d\n", i);
- continue;
- }
-
- /*
- * Flush data and handshake transactions of previous
- * setup packet.
- */
- _ep_nuke(ci->ep0out);
- _ep_nuke(ci->ep0in);
-
- /* read_setup_packet */
- do {
- hw_test_and_set_setup_guard(ci);
- memcpy(&req, &hwep->qh.ptr->setup, sizeof(req));
- } while (!hw_test_and_clear_setup_guard(ci));
-
- type = req.bRequestType;
-
- ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX;
-
- switch (req.bRequest) {
- case USB_REQ_CLEAR_FEATURE:
- if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) &&
- le16_to_cpu(req.wValue) ==
- USB_ENDPOINT_HALT) {
- if (req.wLength != 0)
- break;
- num = le16_to_cpu(req.wIndex);
- dir = num & USB_ENDPOINT_DIR_MASK;
- num &= USB_ENDPOINT_NUMBER_MASK;
- if (dir) /* TX */
- num += ci->hw_ep_max/2;
- if (!ci->ci_hw_ep[num].wedge) {
- spin_unlock(&ci->lock);
- err = usb_ep_clear_halt(
- &ci->ci_hw_ep[num].ep);
- spin_lock(&ci->lock);
- if (err)
- break;
- }
- err = isr_setup_status_phase(ci);
- } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) &&
- le16_to_cpu(req.wValue) ==
- USB_DEVICE_REMOTE_WAKEUP) {
- if (req.wLength != 0)
- break;
- ci->remote_wakeup = 0;
- err = isr_setup_status_phase(ci);
- } else {
- goto delegate;
- }
- break;
- case USB_REQ_GET_STATUS:
- if (type != (USB_DIR_IN|USB_RECIP_DEVICE) &&
- type != (USB_DIR_IN|USB_RECIP_ENDPOINT) &&
- type != (USB_DIR_IN|USB_RECIP_INTERFACE))
- goto delegate;
- if (le16_to_cpu(req.wLength) != 2 ||
- le16_to_cpu(req.wValue) != 0)
- break;
- err = isr_get_status_response(ci, &req);
- break;
- case USB_REQ_SET_ADDRESS:
- if (type != (USB_DIR_OUT|USB_RECIP_DEVICE))
- goto delegate;
- if (le16_to_cpu(req.wLength) != 0 ||
- le16_to_cpu(req.wIndex) != 0)
- break;
- ci->address = (u8)le16_to_cpu(req.wValue);
- ci->setaddr = true;
- err = isr_setup_status_phase(ci);
- break;
- case USB_REQ_SET_FEATURE:
- if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) &&
- le16_to_cpu(req.wValue) ==
- USB_ENDPOINT_HALT) {
- if (req.wLength != 0)
- break;
- num = le16_to_cpu(req.wIndex);
- dir = num & USB_ENDPOINT_DIR_MASK;
- num &= USB_ENDPOINT_NUMBER_MASK;
- if (dir) /* TX */
- num += ci->hw_ep_max/2;
-
- spin_unlock(&ci->lock);
- err = usb_ep_set_halt(&ci->ci_hw_ep[num].ep);
- spin_lock(&ci->lock);
- if (!err)
- isr_setup_status_phase(ci);
- } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) {
- if (req.wLength != 0)
- break;
- switch (le16_to_cpu(req.wValue)) {
- case USB_DEVICE_REMOTE_WAKEUP:
- ci->remote_wakeup = 1;
- err = isr_setup_status_phase(ci);
- break;
- case USB_DEVICE_TEST_MODE:
- tmode = le16_to_cpu(req.wIndex) >> 8;
- switch (tmode) {
- case TEST_J:
- case TEST_K:
- case TEST_SE0_NAK:
- case TEST_PACKET:
- case TEST_FORCE_EN:
- ci->test_mode = tmode;
- err = isr_setup_status_phase(
- ci);
- break;
- default:
- break;
- }
- default:
- goto delegate;
- }
- } else {
- goto delegate;
- }
- break;
- default:
-delegate:
- if (req.wLength == 0) /* no data phase */
- ci->ep0_dir = TX;
-
- spin_unlock(&ci->lock);
- err = ci->driver->setup(&ci->gadget, &req);
- spin_lock(&ci->lock);
- break;
- }
-
- if (err < 0) {
- spin_unlock(&ci->lock);
- if (usb_ep_set_halt(&hwep->ep))
- dev_err(ci->dev, "error: ep_set_halt\n");
- spin_lock(&ci->lock);
- }
+ /* Only handle setup packet below */
+ if (i == 0 &&
+ hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0)))
+ isr_setup_packet_handler(ci);
}
}
@@ -1169,14 +1169,25 @@ static int ep_enable(struct usb_ep *ep,
if (hwep->type == USB_ENDPOINT_XFER_CONTROL)
cap |= QH_IOS;
- if (hwep->num)
- cap |= QH_ZLT;
+
+ cap |= QH_ZLT;
cap |= (hwep->ep.maxpacket << __ffs(QH_MAX_PKT)) & QH_MAX_PKT;
+ /*
+ * For ISO-TX, we set mult at QH as the largest value, and use
+ * MultO at TD as real mult value.
+ */
+ if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX)
+ cap |= 3 << __ffs(QH_MULT);
hwep->qh.ptr->cap = cpu_to_le32(cap);
hwep->qh.ptr->td.next |= cpu_to_le32(TD_TERMINATE); /* needed? */
+ if (hwep->num != 0 && hwep->type == USB_ENDPOINT_XFER_CONTROL) {
+ dev_err(hwep->ci->dev, "Set control xfer at non-ep0\n");
+ retval = -EINVAL;
+ }
+
/*
* Enable endpoints in the HW other than ep0 as ep0
* is always enabled
@@ -1310,6 +1321,7 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep);
struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req);
unsigned long flags;
+ struct td_node *node, *tmpnode;
if (ep == NULL || req == NULL || hwreq->req.status != -EALREADY ||
hwep->ep.desc == NULL || list_empty(&hwreq->queue) ||
@@ -1320,6 +1332,12 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
+ list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) {
+ dma_pool_free(hwep->td_pool, node->ptr, node->dma);
+ list_del(&node->td);
+ kfree(node);
+ }
+
/* pop request */
list_del_init(&hwreq->queue);
@@ -1459,15 +1477,17 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
pm_runtime_get_sync(&_gadget->dev);
hw_device_reset(ci, USBMODE_CM_DC);
hw_device_state(ci, ci->ep0out->qh.dma);
- dev_dbg(ci->dev, "Connected to host\n");
+ usb_gadget_set_state(_gadget, USB_STATE_POWERED);
} else {
+ if (ci->driver)
+ ci->driver->disconnect(&ci->gadget);
hw_device_state(ci, 0);
if (ci->platdata->notify_event)
ci->platdata->notify_event(ci,
CI_HDRC_CONTROLLER_STOPPED_EVENT);
_gadget_stop_activity(&ci->gadget);
pm_runtime_put_sync(&_gadget->dev);
- dev_dbg(ci->dev, "Disconnected from host\n");
+ usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED);
}
}
@@ -1563,7 +1583,7 @@ static int init_eps(struct ci_hdrc *ci)
* eps, maxP is set by epautoconfig() called
* by gadget layer
*/
- hwep->ep.maxpacket = (unsigned short)~0;
+ usb_ep_set_maxpacket_limit(&hwep->ep, (unsigned short)~0);
INIT_LIST_HEAD(&hwep->qh.queue);
hwep->qh.ptr = dma_pool_alloc(ci->qh_pool, GFP_KERNEL,
@@ -1583,7 +1603,7 @@ static int init_eps(struct ci_hdrc *ci)
else
ci->ep0in = hwep;
- hwep->ep.maxpacket = CTRL_PAYLOAD_MAX;
+ usb_ep_set_maxpacket_limit(&hwep->ep, CTRL_PAYLOAD_MAX);
continue;
}
@@ -1600,6 +1620,8 @@ static void destroy_eps(struct ci_hdrc *ci)
for (i = 0; i < ci->hw_ep_max; i++) {
struct ci_hw_ep *hwep = &ci->ci_hw_ep[i];
+ if (hwep->pending_td)
+ free_pending_td(hwep);
dma_pool_free(ci->qh_pool, hwep->qh.ptr, hwep->qh.dma);
}
}
@@ -1631,23 +1653,29 @@ static int ci_udc_start(struct usb_gadget *gadget,
retval = usb_ep_enable(&ci->ep0in->ep);
if (retval)
return retval;
- spin_lock_irqsave(&ci->lock, flags);
ci->driver = driver;
+
+ /* Start otg fsm for B-device */
+ if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) {
+ ci_hdrc_otg_fsm_start(ci);
+ return retval;
+ }
+
pm_runtime_get_sync(&ci->gadget.dev);
if (ci->vbus_active) {
+ spin_lock_irqsave(&ci->lock, flags);
hw_device_reset(ci, USBMODE_CM_DC);
} else {
pm_runtime_put_sync(&ci->gadget.dev);
- goto done;
+ return retval;
}
retval = hw_device_state(ci, ci->ep0out->qh.dma);
+ spin_unlock_irqrestore(&ci->lock, flags);
if (retval)
pm_runtime_put_sync(&ci->gadget.dev);
- done:
- spin_unlock_irqrestore(&ci->lock, flags);
return retval;
}
@@ -1667,13 +1695,13 @@ static int ci_udc_stop(struct usb_gadget *gadget,
if (ci->platdata->notify_event)
ci->platdata->notify_event(ci,
CI_HDRC_CONTROLLER_STOPPED_EVENT);
- ci->driver = NULL;
spin_unlock_irqrestore(&ci->lock, flags);
_gadget_stop_activity(&ci->gadget);
spin_lock_irqsave(&ci->lock, flags);
pm_runtime_put(&ci->gadget.dev);
}
+ ci->driver = NULL;
spin_unlock_irqrestore(&ci->lock, flags);
return 0;
@@ -1732,6 +1760,8 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)
ci->suspended = 1;
spin_unlock(&ci->lock);
ci->driver->suspend(&ci->gadget);
+ usb_gadget_set_state(&ci->gadget,
+ USB_STATE_SUSPENDED);
spin_lock(&ci->lock);
}
}
@@ -1758,7 +1788,7 @@ static int udc_start(struct ci_hdrc *ci)
ci->gadget.ops = &usb_gadget_ops;
ci->gadget.speed = USB_SPEED_UNKNOWN;
ci->gadget.max_speed = USB_SPEED_HIGH;
- ci->gadget.is_otg = 0;
+ ci->gadget.is_otg = ci->is_otg ? 1 : 0;
ci->gadget.name = ci->platdata->name;
INIT_LIST_HEAD(&ci->gadget.ep_list);
@@ -1784,54 +1814,15 @@ static int udc_start(struct ci_hdrc *ci)
ci->gadget.ep0 = &ci->ep0in->ep;
- if (ci->global_phy) {
- ci->transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
- if (IS_ERR(ci->transceiver))
- ci->transceiver = NULL;
- }
-
- if (ci->platdata->flags & CI_HDRC_REQUIRE_TRANSCEIVER) {
- if (ci->transceiver == NULL) {
- retval = -ENODEV;
- goto destroy_eps;
- }
- }
-
- if (ci->transceiver) {
- retval = otg_set_peripheral(ci->transceiver->otg,
- &ci->gadget);
- /*
- * If we implement all USB functions using chipidea drivers,
- * it doesn't need to call above API, meanwhile, if we only
- * use gadget function, calling above API is useless.
- */
- if (retval && retval != -ENOTSUPP)
- goto put_transceiver;
- }
-
retval = usb_add_gadget_udc(dev, &ci->gadget);
if (retval)
- goto remove_trans;
+ goto destroy_eps;
pm_runtime_no_callbacks(&ci->gadget.dev);
pm_runtime_enable(&ci->gadget.dev);
- /* Update ci->vbus_active */
- ci_handle_vbus_change(ci);
-
return retval;
-remove_trans:
- if (ci->transceiver) {
- otg_set_peripheral(ci->transceiver->otg, NULL);
- if (ci->global_phy)
- usb_put_phy(ci->transceiver);
- }
-
- dev_err(dev, "error = %i\n", retval);
-put_transceiver:
- if (ci->transceiver && ci->global_phy)
- usb_put_phy(ci->transceiver);
destroy_eps:
destroy_eps(ci);
free_pools:
@@ -1857,31 +1848,26 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
dma_pool_destroy(ci->td_pool);
dma_pool_destroy(ci->qh_pool);
-
- if (ci->transceiver) {
- otg_set_peripheral(ci->transceiver->otg, NULL);
- if (ci->global_phy)
- usb_put_phy(ci->transceiver);
- }
}
static int udc_id_switch_for_device(struct ci_hdrc *ci)
{
- if (ci->is_otg) {
- ci_clear_otg_interrupt(ci, OTGSC_BSVIS);
- ci_enable_otg_interrupt(ci, OTGSC_BSVIE);
- }
+ if (ci->is_otg)
+ /* Clear and enable BSV irq */
+ hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
+ OTGSC_BSVIS | OTGSC_BSVIE);
return 0;
}
static void udc_id_switch_for_host(struct ci_hdrc *ci)
{
- if (ci->is_otg) {
- /* host doesn't care B_SESSION_VALID event */
- ci_clear_otg_interrupt(ci, OTGSC_BSVIS);
- ci_disable_otg_interrupt(ci, OTGSC_BSVIE);
- }
+ /*
+ * host doesn't care B_SESSION_VALID event
+ * so clear and disbale BSV irq
+ */
+ if (ci->is_otg)
+ hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
}
/**
diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c
index 8a1094b1182..85293b8b1df 100644
--- a/drivers/usb/chipidea/usbmisc_imx.c
+++ b/drivers/usb/chipidea/usbmisc_imx.c
@@ -21,12 +21,39 @@
#define MX25_USB_PHY_CTRL_OFFSET 0x08
#define MX25_BM_EXTERNAL_VBUS_DIVIDER BIT(23)
+#define MX25_EHCI_INTERFACE_SINGLE_UNI (2 << 0)
+#define MX25_EHCI_INTERFACE_DIFF_UNI (0 << 0)
+#define MX25_EHCI_INTERFACE_MASK (0xf)
+
+#define MX25_OTG_SIC_SHIFT 29
+#define MX25_OTG_SIC_MASK (0x3 << MX25_OTG_SIC_SHIFT)
+#define MX25_OTG_PM_BIT BIT(24)
+#define MX25_OTG_PP_BIT BIT(11)
+#define MX25_OTG_OCPOL_BIT BIT(3)
+
+#define MX25_H1_SIC_SHIFT 21
+#define MX25_H1_SIC_MASK (0x3 << MX25_H1_SIC_SHIFT)
+#define MX25_H1_PP_BIT BIT(18)
+#define MX25_H1_PM_BIT BIT(16)
+#define MX25_H1_IPPUE_UP_BIT BIT(7)
+#define MX25_H1_IPPUE_DOWN_BIT BIT(6)
+#define MX25_H1_TLL_BIT BIT(5)
+#define MX25_H1_USBTE_BIT BIT(4)
+#define MX25_H1_OCPOL_BIT BIT(2)
+
+#define MX27_H1_PM_BIT BIT(8)
+#define MX27_H2_PM_BIT BIT(16)
+#define MX27_OTG_PM_BIT BIT(24)
+
#define MX53_USB_OTG_PHY_CTRL_0_OFFSET 0x08
+#define MX53_USB_OTG_PHY_CTRL_1_OFFSET 0x0c
#define MX53_USB_UH2_CTRL_OFFSET 0x14
#define MX53_USB_UH3_CTRL_OFFSET 0x18
#define MX53_BM_OVER_CUR_DIS_H1 BIT(5)
#define MX53_BM_OVER_CUR_DIS_OTG BIT(8)
#define MX53_BM_OVER_CUR_DIS_UHx BIT(30)
+#define MX53_USB_PHYCTRL1_PLLDIV_MASK 0x3
+#define MX53_USB_PLL_DIV_24_MHZ 0x01
#define MX6_BM_OVER_CUR_DIS BIT(7)
@@ -46,6 +73,39 @@ struct imx_usbmisc {
static struct imx_usbmisc *usbmisc;
+static int usbmisc_imx25_init(struct imx_usbmisc_data *data)
+{
+ unsigned long flags;
+ u32 val = 0;
+
+ if (data->index > 1)
+ return -EINVAL;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ switch (data->index) {
+ case 0:
+ val = readl(usbmisc->base);
+ val &= ~(MX25_OTG_SIC_MASK | MX25_OTG_PP_BIT);
+ val |= (MX25_EHCI_INTERFACE_DIFF_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_OTG_SIC_SHIFT;
+ val |= (MX25_OTG_PM_BIT | MX25_OTG_OCPOL_BIT);
+ writel(val, usbmisc->base);
+ break;
+ case 1:
+ val = readl(usbmisc->base);
+ val &= ~(MX25_H1_SIC_MASK | MX25_H1_PP_BIT | MX25_H1_IPPUE_UP_BIT);
+ val |= (MX25_EHCI_INTERFACE_SINGLE_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_H1_SIC_SHIFT;
+ val |= (MX25_H1_PM_BIT | MX25_H1_OCPOL_BIT | MX25_H1_TLL_BIT |
+ MX25_H1_USBTE_BIT | MX25_H1_IPPUE_DOWN_BIT);
+
+ writel(val, usbmisc->base);
+
+ break;
+ }
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ return 0;
+}
+
static int usbmisc_imx25_post(struct imx_usbmisc_data *data)
{
void __iomem *reg;
@@ -68,6 +128,36 @@ static int usbmisc_imx25_post(struct imx_usbmisc_data *data)
return 0;
}
+static int usbmisc_imx27_init(struct imx_usbmisc_data *data)
+{
+ unsigned long flags;
+ u32 val;
+
+ switch (data->index) {
+ case 0:
+ val = MX27_OTG_PM_BIT;
+ break;
+ case 1:
+ val = MX27_H1_PM_BIT;
+ break;
+ case 2:
+ val = MX27_H2_PM_BIT;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ if (data->disable_oc)
+ val = readl(usbmisc->base) | val;
+ else
+ val = readl(usbmisc->base) & ~val;
+ writel(val, usbmisc->base);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ return 0;
+}
+
static int usbmisc_imx53_init(struct imx_usbmisc_data *data)
{
void __iomem *reg = NULL;
@@ -77,6 +167,13 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data)
if (data->index > 3)
return -EINVAL;
+ /* Select a 24 MHz reference clock for the PHY */
+ reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET;
+ val = readl(reg);
+ val &= ~MX53_USB_PHYCTRL1_PLLDIV_MASK;
+ val |= MX53_USB_PLL_DIV_24_MHZ;
+ writel(val, usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET);
+
if (data->disable_oc) {
spin_lock_irqsave(&usbmisc->lock, flags);
switch (data->index) {
@@ -125,9 +222,14 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data)
}
static const struct usbmisc_ops imx25_usbmisc_ops = {
+ .init = usbmisc_imx25_init,
.post = usbmisc_imx25_post,
};
+static const struct usbmisc_ops imx27_usbmisc_ops = {
+ .init = usbmisc_imx27_init,
+};
+
static const struct usbmisc_ops imx53_usbmisc_ops = {
.init = usbmisc_imx53_init,
};
@@ -162,6 +264,18 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = {
.data = &imx25_usbmisc_ops,
},
{
+ .compatible = "fsl,imx35-usbmisc",
+ .data = &imx25_usbmisc_ops,
+ },
+ {
+ .compatible = "fsl,imx27-usbmisc",
+ .data = &imx27_usbmisc_ops,
+ },
+ {
+ .compatible = "fsl,imx51-usbmisc",
+ .data = &imx53_usbmisc_ops,
+ },
+ {
.compatible = "fsl,imx53-usbmisc",
.data = &imx53_usbmisc_ops,
},