summaryrefslogtreecommitdiffstats
path: root/drivers/usb/chipidea
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r--drivers/usb/chipidea/Kconfig1
-rw-r--r--drivers/usb/chipidea/Makefile4
-rw-r--r--drivers/usb/chipidea/ci.h1
-rw-r--r--drivers/usb/chipidea/ci13xxx_imx.c71
-rw-r--r--drivers/usb/chipidea/ci13xxx_imx.h28
-rw-r--r--drivers/usb/chipidea/core.c24
-rw-r--r--drivers/usb/chipidea/udc.c39
-rw-r--r--drivers/usb/chipidea/usbmisc_imx6q.c162
8 files changed, 318 insertions, 12 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig
index 47e499c..1ea932a 100644
--- a/drivers/usb/chipidea/Kconfig
+++ b/drivers/usb/chipidea/Kconfig
@@ -13,7 +13,6 @@ if USB_CHIPIDEA
config USB_CHIPIDEA_UDC
bool "ChipIdea device controller"
depends on USB_GADGET=y || USB_GADGET=USB_CHIPIDEA
- select USB_GADGET_DUALSPEED
help
Say Y here to enable device controller functionality of the
ChipIdea driver.
diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile
index 5c66d9c..d92ca32 100644
--- a/drivers/usb/chipidea/Makefile
+++ b/drivers/usb/chipidea/Makefile
@@ -1,3 +1,5 @@
+ccflags-$(CONFIG_USB_CHIPIDEA_DEBUG) := -DDEBUG
+
obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o
ci_hdrc-y := core.o
@@ -15,5 +17,5 @@ ifneq ($(CONFIG_PCI),)
endif
ifneq ($(CONFIG_OF_DEVICE),)
- obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_imx.o
+ obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_imx.o usbmisc_imx6q.o
endif
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index d738603..e25d126 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -139,6 +139,7 @@ struct ci13xxx {
enum ci_role role;
bool is_otg;
struct work_struct work;
+ struct work_struct vbus_work;
struct workqueue_struct *wq;
struct dma_pool *qh_pool;
diff --git a/drivers/usb/chipidea/ci13xxx_imx.c b/drivers/usb/chipidea/ci13xxx_imx.c
index ef60d06..0f5ca4b 100644
--- a/drivers/usb/chipidea/ci13xxx_imx.c
+++ b/drivers/usb/chipidea/ci13xxx_imx.c
@@ -20,8 +20,10 @@
#include <linux/usb/chipidea.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
+#include <linux/pinctrl/consumer.h>
#include "ci.h"
+#include "ci13xxx_imx.h"
#define pdev_to_phy(pdev) \
((struct usb_phy *)platform_get_drvdata(pdev))
@@ -34,6 +36,55 @@ struct ci13xxx_imx_data {
struct regulator *reg_vbus;
};
+static const struct usbmisc_ops *usbmisc_ops;
+
+/* Common functions shared by usbmisc drivers */
+
+int usbmisc_set_ops(const struct usbmisc_ops *ops)
+{
+ if (usbmisc_ops)
+ return -EBUSY;
+
+ usbmisc_ops = ops;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usbmisc_set_ops);
+
+void usbmisc_unset_ops(const struct usbmisc_ops *ops)
+{
+ usbmisc_ops = NULL;
+}
+EXPORT_SYMBOL_GPL(usbmisc_unset_ops);
+
+int usbmisc_get_init_data(struct device *dev, struct usbmisc_usb_device *usbdev)
+{
+ struct device_node *np = dev->of_node;
+ struct of_phandle_args args;
+ int ret;
+
+ usbdev->dev = dev;
+
+ ret = of_parse_phandle_with_args(np, "fsl,usbmisc", "#index-cells",
+ 0, &args);
+ if (ret) {
+ dev_err(dev, "Failed to parse property fsl,usbmisc, errno %d\n",
+ ret);
+ memset(usbdev, 0, sizeof(*usbdev));
+ return ret;
+ }
+ usbdev->index = args.args[0];
+ of_node_put(args.np);
+
+ if (of_find_property(np, "disable-over-current", NULL))
+ usbdev->disable_oc = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usbmisc_get_init_data);
+
+/* End of common functions shared by usbmisc drivers*/
+
static struct ci13xxx_platform_data ci13xxx_imx_platdata __devinitdata = {
.name = "ci13xxx_imx",
.flags = CI13XXX_REQUIRE_TRANSCEIVER |
@@ -49,8 +100,13 @@ static int __devinit ci13xxx_imx_probe(struct platform_device *pdev)
struct device_node *phy_np;
struct resource *res;
struct regulator *reg_vbus;
+ struct pinctrl *pinctrl;
int ret;
+ if (of_find_property(pdev->dev.of_node, "fsl,usbmisc", NULL)
+ && !usbmisc_ops)
+ return -EPROBE_DEFER;
+
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
dev_err(&pdev->dev, "Failed to allocate CI13xxx-IMX data!\n");
@@ -63,6 +119,11 @@ static int __devinit ci13xxx_imx_probe(struct platform_device *pdev)
return -ENOENT;
}
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl))
+ dev_warn(&pdev->dev, "pinctrl get/select failed, err=%ld\n",
+ PTR_ERR(pinctrl));
+
data->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(data->clk)) {
dev_err(&pdev->dev,
@@ -120,6 +181,16 @@ static int __devinit ci13xxx_imx_probe(struct platform_device *pdev)
*pdev->dev.dma_mask = DMA_BIT_MASK(32);
dma_set_coherent_mask(&pdev->dev, *pdev->dev.dma_mask);
}
+
+ if (usbmisc_ops && usbmisc_ops->init) {
+ ret = usbmisc_ops->init(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "usbmisc init failed, ret=%d\n", ret);
+ goto err;
+ }
+ }
+
plat_ci = ci13xxx_add_device(&pdev->dev,
pdev->resource, pdev->num_resources,
&ci13xxx_imx_platdata);
diff --git a/drivers/usb/chipidea/ci13xxx_imx.h b/drivers/usb/chipidea/ci13xxx_imx.h
new file mode 100644
index 0000000..2e88acc
--- /dev/null
+++ b/drivers/usb/chipidea/ci13xxx_imx.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/* Used to set SoC specific callbacks */
+struct usbmisc_ops {
+ /* It's called once when probe a usb device */
+ int (*init)(struct device *dev);
+};
+
+struct usbmisc_usb_device {
+ struct device *dev; /* usb controller device */
+ int index;
+
+ int disable_oc:1; /* over current detect disabled */
+};
+
+int usbmisc_set_ops(const struct usbmisc_ops *ops);
+void usbmisc_unset_ops(const struct usbmisc_ops *ops);
+int
+usbmisc_get_init_data(struct device *dev, struct usbmisc_usb_device *usbdev);
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 1083585..f69d029 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -273,14 +273,13 @@ static void ci_role_work(struct work_struct *work)
struct ci13xxx *ci = container_of(work, struct ci13xxx, work);
enum ci_role role = ci_otg_role(ci);
- hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS);
-
if (role != ci->role) {
dev_dbg(ci->dev, "switching from %s to %s\n",
ci_role(ci)->name, ci->roles[role]->name);
ci_role_stop(ci);
ci_role_start(ci, role);
+ enable_irq(ci->irq);
}
}
@@ -320,17 +319,22 @@ static irqreturn_t ci_irq(int irq, void *data)
{
struct ci13xxx *ci = data;
irqreturn_t ret = IRQ_NONE;
+ u32 otgsc = 0;
+
+ if (ci->is_otg)
+ otgsc = hw_read(ci, OP_OTGSC, ~0);
- if (ci->is_otg) {
- u32 sts = hw_read(ci, OP_OTGSC, ~0);
+ if (ci->role != CI_ROLE_END)
+ ret = ci_role(ci)->irq(ci);
- if (sts & OTGSC_IDIS) {
- queue_work(ci->wq, &ci->work);
- ret = IRQ_HANDLED;
- }
+ if (ci->is_otg && (otgsc & OTGSC_IDIS)) {
+ hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS);
+ disable_irq_nosync(ci->irq);
+ queue_work(ci->wq, &ci->work);
+ ret = IRQ_HANDLED;
}
- return ci->role == CI_ROLE_END ? ret : ci_role(ci)->irq(ci);
+ return ret;
}
static DEFINE_IDA(ci_ida);
@@ -462,6 +466,8 @@ static int __devinit ci_hdrc_probe(struct platform_device *pdev)
if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
ci->is_otg = true;
+ /* ID pin needs 1ms debouce time, we delay 2ms for safe */
+ mdelay(2);
ci->role = ci_otg_role(ci);
} else {
ci->role = ci->roles[CI_ROLE_HOST]
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index d214448..2f45bba 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -305,6 +305,18 @@ static u32 hw_test_and_clear_intr_active(struct ci13xxx *ci)
return reg;
}
+static void hw_enable_vbus_intr(struct ci13xxx *ci)
+{
+ hw_write(ci, OP_OTGSC, OTGSC_AVVIS, OTGSC_AVVIS);
+ hw_write(ci, OP_OTGSC, OTGSC_AVVIE, OTGSC_AVVIE);
+ queue_work(ci->wq, &ci->vbus_work);
+}
+
+static void hw_disable_vbus_intr(struct ci13xxx *ci)
+{
+ hw_write(ci, OP_OTGSC, OTGSC_AVVIE, 0);
+}
+
/**
* hw_test_and_clear_setup_guard: test & clear setup guard (execute without
* interruption)
@@ -371,6 +383,16 @@ static int hw_usb_reset(struct ci13xxx *ci)
return 0;
}
+static void vbus_work(struct work_struct *work)
+{
+ struct ci13xxx *ci = container_of(work, struct ci13xxx, vbus_work);
+
+ if (hw_read(ci, OP_OTGSC, OTGSC_AVV))
+ usb_gadget_vbus_connect(&ci->gadget);
+ else
+ usb_gadget_vbus_disconnect(&ci->gadget);
+}
+
/******************************************************************************
* UTIL block
*****************************************************************************/
@@ -1370,6 +1392,7 @@ static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active)
if (is_active) {
pm_runtime_get_sync(&_gadget->dev);
hw_device_reset(ci, USBMODE_CM_DC);
+ hw_enable_vbus_intr(ci);
hw_device_state(ci, ci->ep0out->qh.dma);
} else {
hw_device_state(ci, 0);
@@ -1544,8 +1567,10 @@ static int ci13xxx_start(struct usb_gadget *gadget,
pm_runtime_get_sync(&ci->gadget.dev);
if (ci->platdata->flags & CI13XXX_PULLUP_ON_VBUS) {
if (ci->vbus_active) {
- if (ci->platdata->flags & CI13XXX_REGS_SHARED)
+ if (ci->platdata->flags & CI13XXX_REGS_SHARED) {
hw_device_reset(ci, USBMODE_CM_DC);
+ hw_enable_vbus_intr(ci);
+ }
} else {
pm_runtime_put_sync(&ci->gadget.dev);
goto done;
@@ -1651,6 +1676,13 @@ static irqreturn_t udc_irq(struct ci13xxx *ci)
} else {
retval = IRQ_NONE;
}
+
+ intr = hw_read(ci, OP_OTGSC, ~0);
+ hw_write(ci, OP_OTGSC, ~0, intr);
+
+ if (intr & (OTGSC_AVVIE & OTGSC_AVVIS))
+ queue_work(ci->wq, &ci->vbus_work);
+
spin_unlock(&ci->lock);
return retval;
@@ -1726,6 +1758,7 @@ static int udc_start(struct ci13xxx *ci)
retval = hw_device_reset(ci, USBMODE_CM_DC);
if (retval)
goto put_transceiver;
+ hw_enable_vbus_intr(ci);
}
retval = device_register(&ci->gadget.dev);
@@ -1788,6 +1821,9 @@ static void udc_stop(struct ci13xxx *ci)
if (ci == NULL)
return;
+ hw_disable_vbus_intr(ci);
+ cancel_work_sync(&ci->vbus_work);
+
usb_del_gadget_udc(&ci->gadget);
destroy_eps(ci);
@@ -1828,6 +1864,7 @@ int ci_hdrc_gadget_init(struct ci13xxx *ci)
rdrv->irq = udc_irq;
rdrv->name = "gadget";
ci->roles[CI_ROLE_GADGET] = rdrv;
+ INIT_WORK(&ci->vbus_work, vbus_work);
return 0;
}
diff --git a/drivers/usb/chipidea/usbmisc_imx6q.c b/drivers/usb/chipidea/usbmisc_imx6q.c
new file mode 100644
index 0000000..416e3fc
--- /dev/null
+++ b/drivers/usb/chipidea/usbmisc_imx6q.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+
+#include "ci13xxx_imx.h"
+
+#define USB_DEV_MAX 4
+
+#define BM_OVER_CUR_DIS BIT(7)
+
+struct imx6q_usbmisc {
+ void __iomem *base;
+ spinlock_t lock;
+ struct clk *clk;
+ struct usbmisc_usb_device usbdev[USB_DEV_MAX];
+};
+
+static struct imx6q_usbmisc *usbmisc;
+
+static struct usbmisc_usb_device *get_usbdev(struct device *dev)
+{
+ int i, ret;
+
+ for (i = 0; i < USB_DEV_MAX; i++) {
+ if (usbmisc->usbdev[i].dev == dev)
+ return &usbmisc->usbdev[i];
+ else if (!usbmisc->usbdev[i].dev)
+ break;
+ }
+
+ if (i >= USB_DEV_MAX)
+ return ERR_PTR(-EBUSY);
+
+ ret = usbmisc_get_init_data(dev, &usbmisc->usbdev[i]);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &usbmisc->usbdev[i];
+}
+
+static int usbmisc_imx6q_init(struct device *dev)
+{
+
+ struct usbmisc_usb_device *usbdev;
+ unsigned long flags;
+ u32 reg;
+
+ usbdev = get_usbdev(dev);
+ if (IS_ERR(usbdev))
+ return PTR_ERR(usbdev);
+
+ if (usbdev->disable_oc) {
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ reg = readl(usbmisc->base + usbdev->index * 4);
+ writel(reg | BM_OVER_CUR_DIS,
+ usbmisc->base + usbdev->index * 4);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ }
+
+ return 0;
+}
+
+static const struct usbmisc_ops imx6q_usbmisc_ops = {
+ .init = usbmisc_imx6q_init,
+};
+
+static const struct of_device_id usbmisc_imx6q_dt_ids[] = {
+ { .compatible = "fsl,imx6q-usbmisc"},
+ { /* sentinel */ }
+};
+
+static int __devinit usbmisc_imx6q_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct imx6q_usbmisc *data;
+ int ret;
+
+ if (usbmisc)
+ return -EBUSY;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ spin_lock_init(&data->lock);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data->base = devm_request_and_ioremap(&pdev->dev, res);
+ if (!data->base)
+ return -EADDRNOTAVAIL;
+
+ 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;
+ }
+
+ ret = usbmisc_set_ops(&imx6q_usbmisc_ops);
+ if (ret) {
+ clk_disable_unprepare(data->clk);
+ return ret;
+ }
+
+ usbmisc = data;
+
+ return 0;
+}
+
+static int __devexit usbmisc_imx6q_remove(struct platform_device *pdev)
+{
+ usbmisc_unset_ops(&imx6q_usbmisc_ops);
+ clk_disable_unprepare(usbmisc->clk);
+ return 0;
+}
+
+static struct platform_driver usbmisc_imx6q_driver = {
+ .probe = usbmisc_imx6q_probe,
+ .remove = __devexit_p(usbmisc_imx6q_remove),
+ .driver = {
+ .name = "usbmisc_imx6q",
+ .owner = THIS_MODULE,
+ .of_match_table = usbmisc_imx6q_dt_ids,
+ },
+};
+
+int __init usbmisc_imx6q_drv_init(void)
+{
+ return platform_driver_register(&usbmisc_imx6q_driver);
+}
+subsys_initcall(usbmisc_imx6q_drv_init);
+
+void __exit usbmisc_imx6q_drv_exit(void)
+{
+ platform_driver_unregister(&usbmisc_imx6q_driver);
+}
+module_exit(usbmisc_imx6q_drv_exit);
+
+MODULE_ALIAS("platform:usbmisc-imx6q");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("driver for imx6q usb non-core registers");
+MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>");
OpenPOWER on IntegriCloud