diff options
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r-- | drivers/usb/chipidea/Kconfig | 7 | ||||
-rw-r--r-- | drivers/usb/chipidea/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/chipidea/bits.h | 14 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci.h | 8 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_imx.c | 110 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_imx.h | 17 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_msm.c | 1 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 197 | ||||
-rw-r--r-- | drivers/usb/chipidea/host.c | 31 | ||||
-rw-r--r-- | drivers/usb/chipidea/host.h | 6 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg.c | 120 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg.h | 35 | ||||
-rw-r--r-- | drivers/usb/chipidea/udc.c | 78 | ||||
-rw-r--r-- | drivers/usb/chipidea/udc.h | 6 | ||||
-rw-r--r-- | drivers/usb/chipidea/usbmisc_imx.c | 95 |
15 files changed, 482 insertions, 245 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index eb2aa2e5a842..4a851e15e58c 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 || USB_GADGET + depends on (USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET) help Say Y here if your system has a dual role high speed USB controller based on ChipIdea silicon IP. Currently, only the @@ -12,15 +12,14 @@ if USB_CHIPIDEA config USB_CHIPIDEA_UDC bool "ChipIdea device controller" - depends on USB_GADGET=y || USB_CHIPIDEA=m + depends on USB_GADGET help Say Y here to enable device controller functionality of the ChipIdea driver. config USB_CHIPIDEA_HOST bool "ChipIdea host controller" - depends on USB=y - depends on USB_EHCI_HCD=y || USB_CHIPIDEA=m + depends on USB_EHCI_HCD select USB_EHCI_ROOT_HUB_TT help Say Y here to enable host controller functionality of the diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index 6cf5f68dedd8..a99d980454a6 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -2,7 +2,7 @@ ccflags-$(CONFIG_USB_CHIPIDEA_DEBUG) := -DDEBUG obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o -ci_hdrc-y := core.o +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 diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index aefa0261220c..464584c6ccae 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -50,7 +50,7 @@ #define PORTSC_PTC (0x0FUL << 16) /* PTS and PTW for non lpm version only */ #define PORTSC_PTS(d) \ - ((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0)) + (u32)((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0)) #define PORTSC_PTW BIT(28) #define PORTSC_STS BIT(29) @@ -59,7 +59,7 @@ #define DEVLC_PSPD_HS (0x02UL << 25) #define DEVLC_PTW BIT(27) #define DEVLC_STS BIT(28) -#define DEVLC_PTS(d) (((d) & 0x7) << 29) +#define DEVLC_PTS(d) (u32)(((d) & 0x7) << 29) /* Encoding for DEVLC_PTS and PORTSC_PTS */ #define PTS_UTMI 0 @@ -79,11 +79,21 @@ #define OTGSC_ASVIS BIT(18) #define OTGSC_BSVIS BIT(19) #define OTGSC_BSEIS BIT(20) +#define OTGSC_1MSIS BIT(21) +#define OTGSC_DPIS BIT(22) #define OTGSC_IDIE BIT(24) #define OTGSC_AVVIE BIT(25) #define OTGSC_ASVIE BIT(26) #define OTGSC_BSVIE BIT(27) #define OTGSC_BSEIE BIT(28) +#define OTGSC_1MSIE BIT(29) +#define OTGSC_DPIE BIT(30) +#define OTGSC_INT_EN_BITS (OTGSC_IDIE | OTGSC_AVVIE | OTGSC_ASVIE \ + | OTGSC_BSVIE | OTGSC_BSEIE | OTGSC_1MSIE \ + | OTGSC_DPIE) +#define OTGSC_INT_STATUS_BITS (OTGSC_IDIS | OTGSC_AVVIS | OTGSC_ASVIS \ + | OTGSC_BSVIS | OTGSC_BSEIS | OTGSC_1MSIS \ + | OTGSC_DPIS) /* USBMODE */ #define USBMODE_CM (0x03UL << 0) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 33cb29f36e06..1c94fc5257f4 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -132,6 +132,9 @@ struct hw_bank { * @transceiver: pointer to USB PHY, if any * @hcd: pointer to usb_hcd for ehci host driver * @debugfs: root dentry for this controller in debugfs + * @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 */ struct ci_hdrc { struct device *dev; @@ -168,6 +171,8 @@ struct ci_hdrc { struct usb_phy *transceiver; struct usb_hcd *hcd; struct dentry *debugfs; + bool id_event; + bool b_sess_valid_event; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) @@ -303,4 +308,7 @@ int hw_port_test_set(struct ci_hdrc *ci, u8 mode); u8 hw_port_test_get(struct ci_hdrc *ci); +int hw_wait_reg(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask, + u32 value, unsigned int timeout_ms); + #endif /* __DRIVERS_USB_CHIPIDEA_CI_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 14362c00db3f..74d998d9b45b 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -19,70 +19,56 @@ #include <linux/dma-mapping.h> #include <linux/usb/chipidea.h> #include <linux/clk.h> -#include <linux/regulator/consumer.h> #include "ci.h" #include "ci_hdrc_imx.h" -#define pdev_to_phy(pdev) \ - ((struct usb_phy *)platform_get_drvdata(pdev)) - struct ci_hdrc_imx_data { struct usb_phy *phy; struct platform_device *ci_pdev; struct clk *clk; - struct regulator *reg_vbus; + struct imx_usbmisc_data *usbmisc_data; }; -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) +static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev) { struct device_node *np = dev->of_node; struct of_phandle_args args; + struct imx_usbmisc_data *data; int ret; - usbdev->dev = dev; + /* + * In case the fsl,usbmisc property is not present this device doesn't + * need usbmisc. Return NULL (which is no error here) + */ + if (!of_get_property(np, "fsl,usbmisc", NULL)) + return NULL; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return ERR_PTR(-ENOMEM); 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; + return ERR_PTR(ret); } - usbdev->index = args.args[0]; + + data->index = args.args[0]; of_node_put(args.np); if (of_find_property(np, "disable-over-current", NULL)) - usbdev->disable_oc = 1; + data->disable_oc = 1; if (of_find_property(np, "external-vbus-divider", NULL)) - usbdev->evdo = 1; + data->evdo = 1; - return 0; + return data; } -EXPORT_SYMBOL_GPL(usbmisc_get_init_data); /* End of common functions shared by usbmisc drivers*/ @@ -93,27 +79,19 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) .name = "ci_hdrc_imx", .capoffset = DEF_CAPOFFSET, .flags = CI_HDRC_REQUIRE_TRANSCEIVER | - CI_HDRC_PULLUP_ON_VBUS | CI_HDRC_DISABLE_STREAMING, }; - struct resource *res; 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 ci_hdrc-imx data!\n"); return -ENOMEM; } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "Can't get device resources!\n"); - return -ENOENT; - } + data->usbmisc_data = usbmisc_get_init_data(&pdev->dev); + if (IS_ERR(data->usbmisc_data)) + return PTR_ERR(data->usbmisc_data); data->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(data->clk)) { @@ -141,20 +119,6 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) goto err_clk; } - /* we only support host now, so enable vbus here */ - data->reg_vbus = devm_regulator_get(&pdev->dev, "vbus"); - if (!IS_ERR(data->reg_vbus)) { - ret = regulator_enable(data->reg_vbus); - if (ret) { - dev_err(&pdev->dev, - "Failed to enable vbus regulator, err=%d\n", - ret); - goto err_clk; - } - } else { - data->reg_vbus = NULL; - } - pdata.phy = data->phy; if (!pdev->dev.dma_mask) @@ -162,12 +126,12 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) if (!pdev->dev.coherent_dma_mask) pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); - if (usbmisc_ops && usbmisc_ops->init) { - ret = usbmisc_ops->init(&pdev->dev); + if (data->usbmisc_data) { + ret = imx_usbmisc_init(data->usbmisc_data); if (ret) { - dev_err(&pdev->dev, - "usbmisc init failed, ret=%d\n", ret); - goto err; + dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n", + ret); + goto err_clk; } } @@ -179,14 +143,14 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Can't register ci_hdrc platform device, err=%d\n", ret); - goto err; + goto err_clk; } - if (usbmisc_ops && usbmisc_ops->post) { - ret = usbmisc_ops->post(&pdev->dev); + if (data->usbmisc_data) { + ret = imx_usbmisc_init_post(data->usbmisc_data); if (ret) { - dev_err(&pdev->dev, - "usbmisc post failed, ret=%d\n", ret); + dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n", + ret); goto disable_device; } } @@ -200,9 +164,6 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) disable_device: ci_hdrc_remove_device(data->ci_pdev); -err: - if (data->reg_vbus) - regulator_disable(data->reg_vbus); err_clk: clk_disable_unprepare(data->clk); return ret; @@ -215,13 +176,8 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); ci_hdrc_remove_device(data->ci_pdev); - if (data->reg_vbus) - regulator_disable(data->reg_vbus); - - if (data->phy) { + if (data->phy) usb_phy_shutdown(data->phy); - module_put(data->phy->dev->driver->owner); - } clk_disable_unprepare(data->clk); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index 550bfa457620..c7271590dd0a 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -9,23 +9,12 @@ * 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); - /* It's called once after adding a usb device */ - int (*post)(struct device *dev); -}; - -struct usbmisc_usb_device { - struct device *dev; /* usb controller device */ +struct imx_usbmisc_data { int index; unsigned int disable_oc:1; /* over current detect disabled */ unsigned int evdo:1; /* set external vbus divider option */ }; -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); +int imx_usbmisc_init(struct imx_usbmisc_data *); +int imx_usbmisc_init_post(struct imx_usbmisc_data *); diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c index fb657ef50a9c..2d51d852b474 100644 --- a/drivers/usb/chipidea/ci_hdrc_msm.c +++ b/drivers/usb/chipidea/ci_hdrc_msm.c @@ -49,7 +49,6 @@ static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = { .name = "ci_hdrc_msm", .flags = CI_HDRC_REGS_SHARED | CI_HDRC_REQUIRE_TRANSCEIVER | - CI_HDRC_PULLUP_ON_VBUS | CI_HDRC_DISABLE_STREAMING, .notify_event = ci_hdrc_msm_notify_event, diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index a5df24c578fc..94626409559a 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -65,12 +65,14 @@ #include <linux/usb/chipidea.h> #include <linux/usb/of.h> #include <linux/phy.h> +#include <linux/regulator/consumer.h> #include "ci.h" #include "udc.h" #include "bits.h" #include "host.h" #include "debug.h" +#include "otg.h" /* Controller register map */ static uintptr_t ci_regs_nolpm[] = { @@ -197,6 +199,12 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base) if (ci->hw_ep_max > ENDPT_MAX) return -ENODEV; + /* Disable all interrupts bits */ + hw_write(ci, OP_USBINTR, 0xffffffff, 0); + + /* Clear all interrupts status bits*/ + hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff); + dev_dbg(ci->dev, "ChipIdea HDRC found, lpm: %d; cap: %p op: %p\n", ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op); @@ -264,8 +272,6 @@ int hw_device_reset(struct ci_hdrc *ci, u32 mode) while (hw_read(ci, OP_USBCMD, USBCMD_RST)) udelay(10); /* not RTOS friendly */ - hw_phymode_configure(ci); - if (ci->platdata->notify_event) ci->platdata->notify_event(ci, CI_HDRC_CONTROLLER_RESET_EVENT); @@ -289,37 +295,35 @@ int hw_device_reset(struct ci_hdrc *ci, u32 mode) } /** - * ci_otg_role - pick role based on ID pin state + * hw_wait_reg: wait the register value + * + * Sometimes, it needs to wait register value before going on. + * Eg, when switch to device mode, the vbus value should be lower + * than OTGSC_BSV before connects to host. + * * @ci: the controller + * @reg: register index + * @mask: mast bit + * @value: the bit value to wait + * @timeout_ms: timeout in millisecond + * + * This function returns an error code if timeout */ -static 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 - ? CI_ROLE_GADGET - : CI_ROLE_HOST; - - return role; -} - -/** - * ci_role_work - perform role changing based on ID pin - * @work: work struct - */ -static void ci_role_work(struct work_struct *work) +int hw_wait_reg(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask, + u32 value, unsigned int timeout_ms) { - struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); - enum ci_role role = ci_otg_role(ci); - - 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); + unsigned long elapse = jiffies + msecs_to_jiffies(timeout_ms); + + while (hw_read(ci, reg, mask) != value) { + if (time_after(jiffies, elapse)) { + dev_err(ci->dev, "timeout waiting for %08x in %d\n", + mask, reg); + return -ETIMEDOUT; + } + msleep(20); } - enable_irq(ci->irq); + return 0; } static irqreturn_t ci_irq(int irq, void *data) @@ -331,19 +335,55 @@ static irqreturn_t ci_irq(int irq, void *data) if (ci->is_otg) otgsc = hw_read(ci, OP_OTGSC, ~0); - if (ci->role != CI_ROLE_END) - ret = ci_role(ci)->irq(ci); + /* + * Handle id change interrupt, it indicates device/host function + * switch. + */ + 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); + return IRQ_HANDLED; + } - if (ci->is_otg && (otgsc & OTGSC_IDIS)) { - hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS); + /* + * Handle vbus change interrupt, it indicates device connection + * and disconnection events. + */ + 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); - ret = IRQ_HANDLED; + return IRQ_HANDLED; } + /* Handle device/host interrupt */ + if (ci->role != CI_ROLE_END) + ret = ci_role(ci)->irq(ci); + return ret; } +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); + } + + return 0; +} + static DEFINE_IDA(ci_ida); struct platform_device *ci_hdrc_add_device(struct device *dev, @@ -353,6 +393,10 @@ struct platform_device *ci_hdrc_add_device(struct device *dev, struct platform_device *pdev; int id, ret; + ret = ci_get_platdata(dev, platdata); + if (ret) + return ERR_PTR(ret); + id = ida_simple_get(&ci_ida, 0, 0, GFP_KERNEL); if (id < 0) return ERR_PTR(id); @@ -398,6 +442,29 @@ void ci_hdrc_remove_device(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(ci_hdrc_remove_device); +static inline void ci_role_destroy(struct ci_hdrc *ci) +{ + ci_hdrc_gadget_destroy(ci); + ci_hdrc_host_destroy(ci); + if (ci->is_otg) + ci_hdrc_otg_destroy(ci); +} + +static void ci_get_otg_capable(struct ci_hdrc *ci) +{ + if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG) + ci->is_otg = false; + else + ci->is_otg = (hw_read(ci, CAP_DCCPARAMS, + DCCPARAMS_DC | DCCPARAMS_HC) + == (DCCPARAMS_DC | DCCPARAMS_HC)); + 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) { struct device *dev = &pdev->dev; @@ -406,15 +473,13 @@ 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) { dev_err(dev, "platform data missing\n"); return -ENODEV; } - if (!dev->of_node && dev->parent) - dev->of_node = dev->parent->of_node; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) @@ -447,18 +512,15 @@ static int ci_hdrc_probe(struct platform_device *pdev) return -ENODEV; } - INIT_WORK(&ci->work, ci_role_work); - ci->wq = create_singlethread_workqueue("ci_otg"); - if (!ci->wq) { - dev_err(dev, "can't create workqueue\n"); - return -ENODEV; - } + ci_get_otg_capable(ci); if (!ci->platdata->phy_mode) - ci->platdata->phy_mode = of_usb_get_phy_mode(dev->of_node); + 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(dev->of_node); + 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; @@ -479,15 +541,34 @@ 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"); - ret = -ENODEV; - goto rm_wq; + return -ENODEV; + } + + if (ci->is_otg) { + ret = ci_hdrc_otg_init(ci); + if (ret) { + dev_err(dev, "init otg fails, ret = %d\n", ret); + goto stop; + } } 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); + 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); + } else { + /* + * If the controller is not OTG capable, but support + * role switch, the defalt role is gadget, and the + * user can switch it through debugfs. + */ + ci->role = CI_ROLE_GADGET; + } } else { ci->role = ci->roles[CI_ROLE_HOST] ? CI_ROLE_HOST @@ -497,8 +578,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) ret = ci_role_start(ci, ci->role); if (ret) { dev_err(dev, "can't start %s role\n", ci_role(ci)->name); - ret = -ENODEV; - goto rm_wq; + goto stop; } platform_set_drvdata(pdev, ci); @@ -507,19 +587,13 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; - if (ci->is_otg) - hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE); - ret = dbg_create_files(ci); if (!ret) return 0; free_irq(ci->irq, ci); stop: - ci_role_stop(ci); -rm_wq: - flush_workqueue(ci->wq); - destroy_workqueue(ci->wq); + ci_role_destroy(ci); return ret; } @@ -529,10 +603,8 @@ static int ci_hdrc_remove(struct platform_device *pdev) struct ci_hdrc *ci = platform_get_drvdata(pdev); dbg_remove_files(ci); - flush_workqueue(ci->wq); - destroy_workqueue(ci->wq); free_irq(ci->irq, ci); - ci_role_stop(ci); + ci_role_destroy(ci); return 0; } @@ -548,7 +620,6 @@ static struct platform_driver ci_hdrc_driver = { module_platform_driver(ci_hdrc_driver); MODULE_ALIAS("platform:ci_hdrc"); -MODULE_ALIAS("platform:ci13xxx"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("David Lopo <dlopo@chipidea.mips.com>"); MODULE_DESCRIPTION("ChipIdea HDRC Driver"); diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 40d0fda4f66c..6f96795dd20c 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -24,6 +24,7 @@ #include <linux/usb.h> #include <linux/usb/hcd.h> #include <linux/usb/chipidea.h> +#include <linux/regulator/consumer.h> #include "../host/ehci.h" @@ -63,10 +64,21 @@ static int host_start(struct ci_hdrc *ci) ehci = hcd_to_ehci(hcd); ehci->caps = ci->hw_bank.cap; ehci->has_hostpc = ci->hw_bank.lpm; + ehci->has_tdi_phy_lpm = ci->hw_bank.lpm; + + 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); + goto put_hcd; + } + } ret = usb_add_hcd(hcd, 0, 0); if (ret) - usb_put_hcd(hcd); + goto disable_reg; else ci->hcd = hcd; @@ -74,6 +86,14 @@ static int host_start(struct ci_hdrc *ci) hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); return ret; + +disable_reg: + regulator_disable(ci->platdata->reg_vbus); + +put_hcd: + usb_put_hcd(hcd); + + return ret; } static void host_stop(struct ci_hdrc *ci) @@ -82,6 +102,15 @@ static void host_stop(struct ci_hdrc *ci) usb_remove_hcd(hcd); usb_put_hcd(hcd); + if (ci->platdata->reg_vbus) + regulator_disable(ci->platdata->reg_vbus); +} + + +void ci_hdrc_host_destroy(struct ci_hdrc *ci) +{ + if (ci->role == CI_ROLE_HOST) + host_stop(ci); } int ci_hdrc_host_init(struct ci_hdrc *ci) diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h index 058875c15333..5707bf379bfb 100644 --- a/drivers/usb/chipidea/host.h +++ b/drivers/usb/chipidea/host.h @@ -4,6 +4,7 @@ #ifdef CONFIG_USB_CHIPIDEA_HOST int ci_hdrc_host_init(struct ci_hdrc *ci); +void ci_hdrc_host_destroy(struct ci_hdrc *ci); #else @@ -12,6 +13,11 @@ static inline int ci_hdrc_host_init(struct ci_hdrc *ci) return -ENXIO; } +static inline void ci_hdrc_host_destroy(struct ci_hdrc *ci) +{ + +} + #endif #endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */ diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c new file mode 100644 index 000000000000..39bd7ec8bf75 --- /dev/null +++ b/drivers/usb/chipidea/otg.c @@ -0,0 +1,120 @@ +/* + * otg.c - ChipIdea USB IP core OTG driver + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Author: Peter Chen + * + * 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 otgsc register, it may include OTG operation + * in the future. + */ + +#include <linux/usb/otg.h> +#include <linux/usb/gadget.h> +#include <linux/usb/chipidea.h> + +#include "ci.h" +#include "bits.h" +#include "otg.h" + +/** + * ci_otg_role - pick role based on ID pin state + * @ci: the controller + */ +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 + ? CI_ROLE_GADGET + : CI_ROLE_HOST; + + return role; +} + +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) + usb_gadget_vbus_connect(&ci->gadget); + else + usb_gadget_vbus_disconnect(&ci->gadget); +} + +#define CI_VBUS_STABLE_TIMEOUT_MS 5000 +static void ci_handle_id_switch(struct ci_hdrc *ci) +{ + enum ci_role role = ci_otg_role(ci); + + 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); + /* wait vbus lower than OTGSC_BSV */ + hw_wait_reg(ci, OP_OTGSC, OTGSC_BSV, 0, + CI_VBUS_STABLE_TIMEOUT_MS); + ci_role_start(ci, role); + } +} +/** + * ci_otg_work - perform otg (vbus/id) event handle + * @work: work struct + */ +static void ci_otg_work(struct work_struct *work) +{ + struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); + + if (ci->id_event) { + ci->id_event = false; + ci_handle_id_switch(ci); + } else if (ci->b_sess_valid_event) { + ci->b_sess_valid_event = false; + ci_handle_vbus_change(ci); + } else + dev_err(ci->dev, "unexpected event occurs at %s\n", __func__); + + enable_irq(ci->irq); +} + + +/** + * ci_hdrc_otg_init - initialize otg struct + * ci: the controller + */ +int ci_hdrc_otg_init(struct ci_hdrc *ci) +{ + INIT_WORK(&ci->work, ci_otg_work); + ci->wq = create_singlethread_workqueue("ci_otg"); + if (!ci->wq) { + dev_err(ci->dev, "can't create workqueue\n"); + return -ENODEV; + } + + return 0; +} + +/** + * ci_hdrc_otg_destroy - destroy otg struct + * ci: the controller + */ +void ci_hdrc_otg_destroy(struct ci_hdrc *ci) +{ + if (ci->wq) { + 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); +} diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h new file mode 100644 index 000000000000..2d9f090733bc --- /dev/null +++ b/drivers/usb/chipidea/otg.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Author: Peter Chen + * + * 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_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); +} + +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); + +#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index e475fcda1d68..6b4c2f2eb946 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -27,6 +27,7 @@ #include "udc.h" #include "bits.h" #include "debug.h" +#include "otg.h" /* control endpoint description */ static const struct usb_endpoint_descriptor @@ -84,8 +85,10 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma) /* interrupt, error, port change, reset, sleep/suspend */ hw_write(ci, OP_USBINTR, ~0, USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); + hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); } else { hw_write(ci, OP_USBINTR, ~0, 0); + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); } return 0; } @@ -1445,9 +1448,6 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) unsigned long flags; int gadget_ready = 0; - if (!(ci->platdata->flags & CI_HDRC_PULLUP_ON_VBUS)) - return -EOPNOTSUPP; - spin_lock_irqsave(&ci->lock, flags); ci->vbus_active = is_active; if (ci->driver) @@ -1459,6 +1459,7 @@ 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"); } else { hw_device_state(ci, 0); if (ci->platdata->notify_event) @@ -1466,6 +1467,7 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) CI_HDRC_CONTROLLER_STOPPED_EVENT); _gadget_stop_activity(&ci->gadget); pm_runtime_put_sync(&_gadget->dev); + dev_dbg(ci->dev, "Disconnected from host\n"); } } @@ -1509,6 +1511,9 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on) { struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + if (!ci->vbus_active) + return -EOPNOTSUPP; + if (is_on) hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); else @@ -1630,14 +1635,11 @@ static int ci_udc_start(struct usb_gadget *gadget, ci->driver = driver; pm_runtime_get_sync(&ci->gadget.dev); - if (ci->platdata->flags & CI_HDRC_PULLUP_ON_VBUS) { - if (ci->vbus_active) { - if (ci->platdata->flags & CI_HDRC_REGS_SHARED) - hw_device_reset(ci, USBMODE_CM_DC); - } else { - pm_runtime_put_sync(&ci->gadget.dev); - goto done; - } + if (ci->vbus_active) { + hw_device_reset(ci, USBMODE_CM_DC); + } else { + pm_runtime_put_sync(&ci->gadget.dev); + goto done; } retval = hw_device_state(ci, ci->ep0out->qh.dma); @@ -1660,8 +1662,7 @@ static int ci_udc_stop(struct usb_gadget *gadget, spin_lock_irqsave(&ci->lock, flags); - if (!(ci->platdata->flags & CI_HDRC_PULLUP_ON_VBUS) || - ci->vbus_active) { + if (ci->vbus_active) { hw_device_state(ci, 0); if (ci->platdata->notify_event) ci->platdata->notify_event(ci, @@ -1796,16 +1797,15 @@ static int udc_start(struct ci_hdrc *ci) } } - if (!(ci->platdata->flags & CI_HDRC_REGS_SHARED)) { - retval = hw_device_reset(ci, USBMODE_CM_DC); - if (retval) - goto put_transceiver; - } - if (ci->transceiver) { retval = otg_set_peripheral(ci->transceiver->otg, &ci->gadget); - if (retval) + /* + * 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; } @@ -1816,6 +1816,9 @@ static int udc_start(struct ci_hdrc *ci) 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: @@ -1839,13 +1842,13 @@ free_qh_pool: } /** - * udc_remove: parent remove must call this to remove UDC + * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC * * No interrupts active, the IRQ has been released */ -static void udc_stop(struct ci_hdrc *ci) +void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) { - if (ci == NULL) + if (!ci->roles[CI_ROLE_GADGET]) return; usb_del_gadget_udc(&ci->gadget); @@ -1860,15 +1863,32 @@ static void udc_stop(struct ci_hdrc *ci) if (ci->global_phy) usb_put_phy(ci->transceiver); } - /* my kobject is dynamic, I swear! */ - memset(&ci->gadget, 0, sizeof(ci->gadget)); +} + +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); + } + + 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); + } } /** * ci_hdrc_gadget_init - initialize device related bits * ci: the controller * - * This function enables the gadget role, if the device is "device capable". + * This function initializes the gadget, if the device is "device capable". */ int ci_hdrc_gadget_init(struct ci_hdrc *ci) { @@ -1881,11 +1901,11 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci) if (!rdrv) return -ENOMEM; - rdrv->start = udc_start; - rdrv->stop = udc_stop; + rdrv->start = udc_id_switch_for_device; + rdrv->stop = udc_id_switch_for_host; rdrv->irq = udc_irq; rdrv->name = "gadget"; ci->roles[CI_ROLE_GADGET] = rdrv; - return 0; + return udc_start(ci); } diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h index 455ac2169226..e66df0020bd4 100644 --- a/drivers/usb/chipidea/udc.h +++ b/drivers/usb/chipidea/udc.h @@ -84,6 +84,7 @@ struct ci_hw_req { #ifdef CONFIG_USB_CHIPIDEA_UDC int ci_hdrc_gadget_init(struct ci_hdrc *ci); +void ci_hdrc_gadget_destroy(struct ci_hdrc *ci); #else @@ -92,6 +93,11 @@ static inline int ci_hdrc_gadget_init(struct ci_hdrc *ci) return -ENXIO; } +static inline void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) +{ + +} + #endif #endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */ diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index ac5a46155200..8a1094b1182f 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -18,8 +18,6 @@ #include "ci_hdrc_imx.h" -#define USB_DEV_MAX 4 - #define MX25_USB_PHY_CTRL_OFFSET 0x08 #define MX25_BM_EXTERNAL_VBUS_DIVIDER BIT(23) @@ -32,51 +30,34 @@ #define MX6_BM_OVER_CUR_DIS BIT(7) +struct usbmisc_ops { + /* It's called once when probe a usb device */ + int (*init)(struct imx_usbmisc_data *data); + /* It's called once after adding a usb device */ + int (*post)(struct imx_usbmisc_data *data); +}; + struct imx_usbmisc { void __iomem *base; spinlock_t lock; struct clk *clk; - struct usbmisc_usb_device usbdev[USB_DEV_MAX]; const struct usbmisc_ops *ops; }; static struct imx_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_imx25_post(struct device *dev) +static int usbmisc_imx25_post(struct imx_usbmisc_data *data) { - struct usbmisc_usb_device *usbdev; void __iomem *reg; unsigned long flags; u32 val; - usbdev = get_usbdev(dev); - if (IS_ERR(usbdev)) - return PTR_ERR(usbdev); + if (data->index > 2) + return -EINVAL; reg = usbmisc->base + MX25_USB_PHY_CTRL_OFFSET; - if (usbdev->evdo) { + if (data->evdo) { spin_lock_irqsave(&usbmisc->lock, flags); val = readl(reg); writel(val | MX25_BM_EXTERNAL_VBUS_DIVIDER, reg); @@ -87,20 +68,18 @@ static int usbmisc_imx25_post(struct device *dev) return 0; } -static int usbmisc_imx53_init(struct device *dev) +static int usbmisc_imx53_init(struct imx_usbmisc_data *data) { - struct usbmisc_usb_device *usbdev; void __iomem *reg = NULL; unsigned long flags; u32 val = 0; - usbdev = get_usbdev(dev); - if (IS_ERR(usbdev)) - return PTR_ERR(usbdev); + if (data->index > 3) + return -EINVAL; - if (usbdev->disable_oc) { + if (data->disable_oc) { spin_lock_irqsave(&usbmisc->lock, flags); - switch (usbdev->index) { + switch (data->index) { case 0: reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET; val = readl(reg) | MX53_BM_OVER_CUR_DIS_OTG; @@ -126,22 +105,19 @@ static int usbmisc_imx53_init(struct device *dev) return 0; } -static int usbmisc_imx6q_init(struct device *dev) +static int usbmisc_imx6q_init(struct imx_usbmisc_data *data) { - - struct usbmisc_usb_device *usbdev; unsigned long flags; u32 reg; - usbdev = get_usbdev(dev); - if (IS_ERR(usbdev)) - return PTR_ERR(usbdev); + if (data->index > 3) + return -EINVAL; - if (usbdev->disable_oc) { + if (data->disable_oc) { spin_lock_irqsave(&usbmisc->lock, flags); - reg = readl(usbmisc->base + usbdev->index * 4); + reg = readl(usbmisc->base + data->index * 4); writel(reg | MX6_BM_OVER_CUR_DIS, - usbmisc->base + usbdev->index * 4); + usbmisc->base + data->index * 4); spin_unlock_irqrestore(&usbmisc->lock, flags); } @@ -160,6 +136,26 @@ static const struct usbmisc_ops imx6q_usbmisc_ops = { .init = usbmisc_imx6q_init, }; +int imx_usbmisc_init(struct imx_usbmisc_data *data) +{ + if (!usbmisc) + return -EPROBE_DEFER; + if (!usbmisc->ops->init) + return 0; + return usbmisc->ops->init(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_init); + +int imx_usbmisc_init_post(struct imx_usbmisc_data *data) +{ + if (!usbmisc) + return -EPROBE_DEFER; + if (!usbmisc->ops->post) + return 0; + return usbmisc->ops->post(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_init_post); + static const struct of_device_id usbmisc_imx_dt_ids[] = { { .compatible = "fsl,imx25-usbmisc", @@ -216,19 +212,12 @@ static int usbmisc_imx_probe(struct platform_device *pdev) of_match_device(usbmisc_imx_dt_ids, &pdev->dev); data->ops = (const struct usbmisc_ops *)tmp_dev->data; usbmisc = data; - ret = usbmisc_set_ops(data->ops); - if (ret) { - usbmisc = NULL; - clk_disable_unprepare(data->clk); - return ret; - } return 0; } static int usbmisc_imx_remove(struct platform_device *pdev) { - usbmisc_unset_ops(usbmisc->ops); clk_disable_unprepare(usbmisc->clk); usbmisc = NULL; return 0; |