diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2013-09-03 11:35:32 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-09-03 11:35:32 -0700 |
commit | b3b49114c80e799af8b08c0c6d1ff886ea843f03 (patch) | |
tree | f59b3dd1d589d1010f55f1d44c808fad7b5f0318 /drivers/usb/chipidea/otg.c | |
parent | 1ccfd5eaf8f0135a0ce030728d1739e0eea4e3ce (diff) | |
parent | b9a1048137f4ae43ee90f61a3f34f0efe863cfeb (diff) |
Merge tag 'usb-3.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB patches from Greg KH:
"Here's the big USB driver pull request for 3.12-rc1
Lots of USB driver fixes and updates. Nothing major, just the normal
xhci, gadget, and other driver changes. Full details in the shortlog"
* tag 'usb-3.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (352 commits)
usbcore: fix incorrect type in assignment in descriptors_changed()
usbcore: compare and release one bos descriptor in usb_reset_and_verify_device()
ehci: remove debugging statement with ehci statistics in ehci_stop()
ehci: remove duplicate debug_async_open() prototype in ehci-dbg.c
ehci: enable debugging code when CONFIG_DYNAMIC_DEBUG is set
ehci: remove ehci_vdbg() verbose debugging statements
Documentation sysfs-bus-usb: Document which files are used by libusb
Documentation sysfs-bus-usb: Document the speed file used by libusb
Documentation sysfs-bus-usb: Move files with known users to stable
USB: fix build error when CONFIG_PM_SLEEP isn't enabled
usb: r8a66597-hcd: use platform_{get,set}_drvdata()
usb: phy-tegra-usb: use platform_{get,set}_drvdata()
usb: acm gadget: Null termintate strings table
dma: cppi41: off by one in desc_to_chan()
xhci: Fix warning introduced by disabling runtime PM.
dev-core: fix build break when DEBUG is enabled
USB: OHCI: Allow runtime PM without system sleep
usb: ohci-at91: remove unnecessary dev_set_drvdata()
usb: renesas_usbhs: use platform_{get,set}_drvdata()
usb: fotg210-udc: use platform_{get,set}_drvdata()
...
Diffstat (limited to 'drivers/usb/chipidea/otg.c')
-rw-r--r-- | drivers/usb/chipidea/otg.c | 120 |
1 files changed, 120 insertions, 0 deletions
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); +} |