| /** |
| * Copyright (C) 2012 Intel Corp. |
| * Author: Jiebing Li |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software Foundation, |
| * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| |
| #include <linux/usb/dwc3-intel-mid.h> |
| #include <linux/usb/phy.h> |
| |
| #include "core.h" |
| #include "gadget.h" |
| #include "io.h" |
| #include "otg.h" |
| |
| #include "debug.h" |
| |
| #include "core.c" |
| #include "ep0.c" |
| #include "gadget.c" |
| |
| /* FLIS register */ |
| #define APBFC_EXIOTG3_MISC0_REG 0xF90FF85C |
| |
| /* Global User Control Register Auto Retry bit*/ |
| #define DWC3_GUCTL_USB_HST_IN_AUTO_RETRY_EN (1 << 14) |
| |
| /* Global Configuration Register */ |
| #define DWC3_GRXTHRCFG_USBRXPKTCNTSEL (1 << 29) |
| #define DWC3_GRXTHRCFG_USBRXPKTCNT(n) (n << 24) |
| #define DWC3_GRXTHRCFG_USBRXPKTCNT_MASK (0xf << 24) |
| #define DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE(n) (n << 19) |
| #define DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE_MASK (0x1f << 19) |
| |
| /** |
| * struct dwc3_dev_data - Structure holding platform related |
| * information |
| * @flis_reg: FLIS register |
| * @grxthrcfg: DWC3 GRXTHCFG register |
| */ |
| struct dwc3_dev_data { |
| struct dwc3 *dwc; |
| void __iomem *flis_reg; |
| u32 grxthrcfg; |
| struct mutex mutex; |
| }; |
| |
| static struct dwc3_dev_data *_dev_data; |
| |
| /* |
| * dwc3_set_fils_reg - set FLIS register |
| * |
| * This is a workaround for OTG3 IP bug of using EP #8 for host mode |
| */ |
| static void dwc3_set_flis_reg(void) |
| { |
| u32 reg; |
| void __iomem *flis_reg; |
| |
| flis_reg = _dev_data->flis_reg; |
| |
| reg = dwc3_readl(flis_reg, DWC3_GLOBALS_REGS_START); |
| reg &= ~(1 << 3); |
| dwc3_writel(flis_reg, DWC3_GLOBALS_REGS_START, reg); |
| } |
| |
| /* |
| * dwc3_disable_multi_packet - set GRXTHRCFG register to disable |
| * reception multi-packet thresholdingfor DWC2.50a. |
| */ |
| static void dwc3_disable_multi_packet(struct dwc3 *dwc) |
| { |
| u32 reg; |
| |
| reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG); |
| _dev_data->grxthrcfg = reg; |
| if (reg) { |
| reg &= ~DWC3_GRXTHRCFG_USBRXPKTCNTSEL; |
| reg &= ~DWC3_GRXTHRCFG_USBRXPKTCNT_MASK; |
| reg &= ~DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE_MASK; |
| |
| dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg); |
| } |
| } |
| |
| /* |
| * dwc3_enable_host_auto_retry - clear Auto Retry Enable bit |
| * for device mode |
| */ |
| static void dwc3_enable_host_auto_retry(struct dwc3 *dwc, bool enable) |
| { |
| u32 reg; |
| |
| reg = dwc3_readl(dwc->regs, DWC3_GUCTL); |
| |
| if (enable) |
| reg |= DWC3_GUCTL_USB_HST_IN_AUTO_RETRY_EN; |
| else |
| reg &= ~DWC3_GUCTL_USB_HST_IN_AUTO_RETRY_EN; |
| |
| dwc3_writel(dwc->regs, DWC3_GUCTL, reg); |
| } |
| |
| static void dwc3_do_extra_change(struct dwc3 *dwc) |
| { |
| dwc3_set_flis_reg(); |
| |
| if (dwc->revision == DWC3_REVISION_250A) |
| dwc3_disable_multi_packet(dwc); |
| |
| dwc3_enable_host_auto_retry(dwc, false); |
| } |
| |
| static void dwc3_enable_hibernation(struct dwc3 *dwc) |
| { |
| u32 num, reg; |
| |
| if (DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1) |
| != DWC3_GHWPARAMS1_EN_PWROPT_HIB) { |
| dev_err(dwc->dev, "Device Mode Hibernation is not supported\n"); |
| return; |
| } |
| |
| num = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS( |
| dwc->hwparams.hwparams4); |
| if (num != 1) |
| dev_err(dwc->dev, "number of scratchpad buffer: %d\n", num); |
| |
| reg = dwc3_readl(dwc->regs, DWC3_GCTL); |
| reg |= DWC3_GCTL_GBLHIBERNATIONEN; |
| dwc3_writel(dwc->regs, DWC3_GCTL, reg); |
| |
| dwc3_send_gadget_generic_command(dwc, DWC3_DGCMD_SET_SCRATCH_ADDR_LO, |
| dwc->scratch_array_dma & 0xffffffffU); |
| } |
| |
| /* |
| * Re-write irq functions. Not use irq thread. Because irqthread has negative |
| * impact on usb performance, especially for usb network performance, USB3 UDP |
| * download performance will drop from 80MB/s to 40MB/s if irqthread is enabled. |
| */ |
| static irqreturn_t dwc3_quirks_process_event_buf(struct dwc3 *dwc, u32 buf) |
| { |
| struct dwc3_event_buffer *evt; |
| u32 count; |
| u32 reg; |
| int left; |
| |
| evt = dwc->ev_buffs[buf]; |
| |
| count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(buf)); |
| count &= DWC3_GEVNTCOUNT_MASK; |
| if (!count) |
| return IRQ_NONE; |
| |
| evt->count = count; |
| |
| /* WORKAROUND: Add 4 us delay workaround to A-unit issue in A0 stepping. |
| * Can be removed after B0. |
| */ |
| if (dwc->is_otg && dwc->revision == DWC3_REVISION_210A) |
| udelay(4); |
| |
| left = evt->count; |
| |
| while (left > 0) { |
| union dwc3_event event; |
| |
| event.raw = *(u32 *) (evt->buf + evt->lpos); |
| |
| dwc3_process_event_entry(dwc, &event); |
| |
| /* |
| * FIXME we wrap around correctly to the next entry as |
| * almost all entries are 4 bytes in size. There is one |
| * entry which has 12 bytes which is a regular entry |
| * followed by 8 bytes data. ATM I don't know how |
| * things are organized if we get next to the a |
| * boundary so I worry about that once we try to handle |
| * that. |
| */ |
| evt->lpos = (evt->lpos + 4) % DWC3_EVENT_BUFFERS_SIZE; |
| left -= 4; |
| |
| dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(buf), 4); |
| } |
| |
| evt->count = 0; |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t dwc3_quirks_interrupt(int irq, void *_dwc) |
| { |
| struct dwc3 *dwc = _dwc; |
| int i; |
| irqreturn_t ret = IRQ_NONE; |
| |
| spin_lock(&dwc->lock); |
| if (dwc->pm_state != PM_ACTIVE) { |
| if (dwc->pm_state == PM_SUSPENDED) { |
| dev_info(dwc->dev, "u2/u3 pmu is received\n"); |
| pm_runtime_get(dwc->dev); |
| dwc->pm_state = PM_RESUMING; |
| ret = IRQ_HANDLED; |
| } |
| goto out; |
| } |
| |
| for (i = 0; i < dwc->num_event_buffers; i++) { |
| irqreturn_t status; |
| |
| status = dwc3_quirks_process_event_buf(dwc, i); |
| if (status == IRQ_HANDLED) |
| ret = status; |
| } |
| |
| out: |
| spin_unlock(&dwc->lock); |
| |
| return ret; |
| } |
| |
| int dwc3_start_peripheral(struct usb_gadget *g) |
| { |
| struct dwc3 *dwc = gadget_to_dwc(g); |
| unsigned long flags; |
| int irq; |
| int ret = 0; |
| |
| pm_runtime_get_sync(dwc->dev); |
| |
| mutex_lock(&_dev_data->mutex); |
| spin_lock_irqsave(&dwc->lock, flags); |
| |
| if (dwc->gadget_driver && dwc->soft_connected) { |
| spin_unlock_irqrestore(&dwc->lock, flags); |
| dwc3_core_init(dwc); |
| spin_lock_irqsave(&dwc->lock, flags); |
| |
| if (dwc->hiber_enabled) |
| dwc3_enable_hibernation(dwc); |
| dwc3_do_extra_change(dwc); |
| dwc3_event_buffers_setup(dwc); |
| ret = dwc3_init_for_enumeration(dwc); |
| if (ret) |
| goto err1; |
| |
| if (dwc->soft_connected) |
| dwc3_gadget_run_stop(dwc, 1); |
| } |
| |
| dwc->pm_state = PM_ACTIVE; |
| |
| spin_unlock_irqrestore(&dwc->lock, flags); |
| |
| irq = platform_get_irq(to_platform_device(dwc->dev), 0); |
| if (dwc->quirks_disable_irqthread) |
| ret = request_irq(irq, dwc3_quirks_interrupt, |
| IRQF_SHARED, "dwc3", dwc); |
| else |
| ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt, |
| IRQF_SHARED, "dwc3", dwc); |
| if (ret) { |
| dev_err(dwc->dev, "failed to request irq #%d --> %d\n", |
| irq, ret); |
| goto err0; |
| } |
| mutex_unlock(&_dev_data->mutex); |
| |
| return 0; |
| |
| err1: |
| spin_unlock_irqrestore(&dwc->lock, flags); |
| err0: |
| mutex_unlock(&_dev_data->mutex); |
| |
| return ret; |
| } |
| |
| int dwc3_stop_peripheral(struct usb_gadget *g) |
| { |
| struct dwc3 *dwc = gadget_to_dwc(g); |
| unsigned long flags; |
| u8 epnum; |
| int irq; |
| |
| mutex_lock(&_dev_data->mutex); |
| spin_lock_irqsave(&dwc->lock, flags); |
| |
| dwc3_stop_active_transfers(dwc); |
| |
| if (dwc->gadget.speed != USB_SPEED_UNKNOWN) { |
| dwc3_disconnect_gadget(dwc); |
| |
| dwc->gadget.speed = USB_SPEED_UNKNOWN; |
| } |
| |
| dwc->start_config_issued = false; |
| |
| /* Clear Run/Stop bit */ |
| dwc3_gadget_run_stop(dwc, 0); |
| dwc3_gadget_keep_conn(dwc, 0); |
| |
| for (epnum = 0; epnum < 2; epnum++) { |
| struct dwc3_ep *dep; |
| |
| dep = dwc->eps[epnum]; |
| |
| if (dep->flags & DWC3_EP_ENABLED) |
| __dwc3_gadget_ep_disable(dep); |
| } |
| |
| dwc3_gadget_disable_irq(dwc); |
| |
| dwc3_event_buffers_cleanup(dwc); |
| |
| if (_dev_data->grxthrcfg && dwc->revision == DWC3_REVISION_250A) { |
| dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, _dev_data->grxthrcfg); |
| _dev_data->grxthrcfg = 0; |
| } |
| |
| dwc3_enable_host_auto_retry(dwc, true); |
| |
| if (dwc->pm_state != PM_SUSPENDED) |
| pm_runtime_put(dwc->dev); |
| |
| dwc->pm_state = PM_DISCONNECTED; |
| spin_unlock_irqrestore(&dwc->lock, flags); |
| |
| irq = platform_get_irq(to_platform_device(dwc->dev), 0); |
| free_irq(irq, dwc); |
| |
| mutex_unlock(&_dev_data->mutex); |
| |
| cancel_delayed_work_sync(&dwc->link_work); |
| |
| return 0; |
| } |
| |
| static int dwc3_device_gadget_pullup(struct usb_gadget *g, int is_on) |
| { |
| struct dwc3 *dwc = gadget_to_dwc(g); |
| unsigned long flags; |
| int ret; |
| |
| /* |
| * FIXME If pm_state is PM_RESUMING, we should wait for it to |
| * become PM_ACTIVE before continue. The chance of hitting |
| * PM_RESUMING is rare, but if so, we'll return directly. |
| * |
| * If some gadget reaches here in atomic context, |
| * pm_runtime_get_sync will cause a sleep problem. |
| */ |
| if (dwc->pm_state == PM_RESUMING) { |
| dev_err(dwc->dev, "%s: PM_RESUMING, return -EIO\n", __func__); |
| return -EIO; |
| } |
| |
| if (dwc->pm_state == PM_SUSPENDED) |
| pm_runtime_get_sync(dwc->dev); |
| |
| is_on = !!is_on; |
| |
| mutex_lock(&_dev_data->mutex); |
| |
| if (dwc->soft_connected == is_on) |
| goto done; |
| |
| dwc->soft_connected = is_on; |
| |
| spin_lock_irqsave(&dwc->lock, flags); |
| if (dwc->pm_state == PM_DISCONNECTED) { |
| spin_unlock_irqrestore(&dwc->lock, flags); |
| goto done; |
| } |
| |
| if (is_on) { |
| /* Per dwc3 databook 2.40a section 8.1.9, re-connection |
| * should follow steps described section 8.1.1 power on |
| * or soft reset. |
| */ |
| spin_unlock_irqrestore(&dwc->lock, flags); |
| dwc3_core_init(dwc); |
| spin_lock_irqsave(&dwc->lock, flags); |
| |
| if (dwc->hiber_enabled) |
| dwc3_enable_hibernation(dwc); |
| dwc3_do_extra_change(dwc); |
| dwc3_event_buffers_setup(dwc); |
| dwc3_init_for_enumeration(dwc); |
| ret = dwc3_gadget_run_stop(dwc, 1); |
| if (dwc->hiber_enabled) |
| dwc3_gadget_keep_conn(dwc, 1); |
| } else { |
| u8 epnum; |
| |
| for (epnum = 0; epnum < 2; epnum++) { |
| struct dwc3_ep *dep; |
| |
| dep = dwc->eps[epnum]; |
| |
| if (dep->flags & DWC3_EP_ENABLED) |
| __dwc3_gadget_ep_disable(dep); |
| } |
| |
| dwc3_stop_active_transfers(dwc); |
| dwc3_gadget_keep_conn(dwc, 0); |
| ret = dwc3_gadget_run_stop(dwc, 0); |
| dwc3_gadget_disable_irq(dwc); |
| } |
| |
| spin_unlock_irqrestore(&dwc->lock, flags); |
| mutex_unlock(&_dev_data->mutex); |
| |
| return ret; |
| |
| done: |
| mutex_unlock(&_dev_data->mutex); |
| |
| return 0; |
| } |
| |
| static const struct usb_gadget_ops dwc3_device_gadget_ops = { |
| .get_frame = dwc3_gadget_get_frame, |
| .wakeup = dwc3_gadget_wakeup, |
| .set_selfpowered = dwc3_gadget_set_selfpowered, |
| .pullup = dwc3_device_gadget_pullup, |
| .udc_start = dwc3_gadget_start, |
| .udc_stop = dwc3_gadget_stop, |
| .vbus_draw = dwc3_vbus_draw, |
| }; |
| |
| static int dwc3_device_intel_probe(struct platform_device *pdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| struct dwc3 *dwc; |
| struct device *dev = &pdev->dev; |
| int ret = -ENOMEM; |
| void *mem; |
| |
| struct dwc_device_par *pdata; |
| struct usb_phy *usb_phy; |
| struct dwc_otg2 *otg; |
| |
| mem = devm_kzalloc(dev, sizeof(*dwc) + DWC3_ALIGN_MASK, GFP_KERNEL); |
| if (!mem) { |
| dev_err(dev, "not enough memory\n"); |
| return -ENOMEM; |
| } |
| dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1); |
| dwc->mem = mem; |
| |
| _dev_data = kzalloc(sizeof(*_dev_data), GFP_KERNEL); |
| if (!_dev_data) { |
| dev_err(dev, "not enough memory\n"); |
| return -ENOMEM; |
| } |
| |
| _dev_data->dwc = dwc; |
| |
| pdata = (struct dwc_device_par *)pdev->dev.platform_data; |
| if (!pdata) { |
| dev_err(&pdev->dev, "No platform data for %s.\n", |
| dev_name(&pdev->dev)); |
| return -ENODEV; |
| } |
| |
| if (node) { |
| dwc->usb2_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0); |
| dwc->usb3_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 1); |
| } else { |
| dwc->usb2_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); |
| dwc->usb3_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB3); |
| } |
| |
| if (IS_ERR(dwc->usb2_phy)) { |
| ret = PTR_ERR(dwc->usb2_phy); |
| |
| /* |
| * 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; |
| } |
| |
| if (IS_ERR(dwc->usb3_phy)) { |
| ret = PTR_ERR(dwc->usb2_phy); |
| |
| /* |
| * 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 usb3 phy configured\n"); |
| return -EPROBE_DEFER; |
| } |
| |
| mutex_init(&_dev_data->mutex); |
| spin_lock_init(&dwc->lock); |
| platform_set_drvdata(pdev, dwc); |
| |
| dwc->regs = pdata->io_addr + DWC3_GLOBALS_REGS_START; |
| dwc->regs_size = pdata->len - DWC3_GLOBALS_REGS_START; |
| dwc->dev = dev; |
| |
| dev->dma_mask = dev->parent->dma_mask; |
| dev->dma_parms = dev->parent->dma_parms; |
| dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask); |
| |
| if (!strncmp("super", maximum_speed, 5)) |
| dwc->maximum_speed = DWC3_DCFG_SUPERSPEED; |
| else if (!strncmp("high", maximum_speed, 4)) |
| dwc->maximum_speed = DWC3_DCFG_HIGHSPEED; |
| else if (!strncmp("full", maximum_speed, 4)) |
| dwc->maximum_speed = DWC3_DCFG_FULLSPEED1; |
| else if (!strncmp("low", maximum_speed, 3)) |
| dwc->maximum_speed = DWC3_DCFG_LOWSPEED; |
| else |
| dwc->maximum_speed = DWC3_DCFG_SUPERSPEED; |
| |
| dwc->needs_fifo_resize = of_property_read_bool(node, "tx-fifo-resize"); |
| |
| pm_runtime_set_active(&pdev->dev); |
| pm_runtime_enable(dev); |
| pm_runtime_set_suspended(&pdev->dev); |
| pm_runtime_get_sync(dev); |
| pm_runtime_forbid(dev); |
| |
| dwc3_cache_hwparams(dwc); |
| dwc3_core_num_eps(dwc); |
| |
| _dev_data->flis_reg = |
| ioremap_nocache(APBFC_EXIOTG3_MISC0_REG, 4); |
| |
| ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE); |
| if (ret) { |
| dev_err(dwc->dev, "failed to allocate event buffers\n"); |
| ret = -ENOMEM; |
| goto err0; |
| } |
| |
| /* |
| * Not use irq thread, because irqthread has negative impact |
| * on usb performance, especially for usb network performance. |
| */ |
| dwc->quirks_disable_irqthread = 1; |
| |
| usb_phy = usb_get_phy(USB_PHY_TYPE_USB2); |
| otg = container_of(usb_phy, struct dwc_otg2, usb2_phy); |
| otg->start_device = dwc3_start_peripheral; |
| otg->stop_device = dwc3_stop_peripheral; |
| otg->vbus_draw = dwc3_vbus_draw; |
| usb_put_phy(usb_phy); |
| dwc->is_otg = 1; |
| |
| dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); |
| ret = dwc3_gadget_init(dwc); |
| if (ret) { |
| dev_err(dev, "failed to initialize gadget\n"); |
| goto err0; |
| } |
| dwc->gadget.ops = &dwc3_device_gadget_ops; |
| dwc->gadget.is_otg = 1; |
| |
| dwc->mode = DWC3_MODE_DEVICE; |
| |
| ret = dwc3_debugfs_init(dwc); |
| if (ret) { |
| dev_err(dev, "failed to initialize debugfs\n"); |
| goto err1; |
| } |
| |
| pm_runtime_allow(dev); |
| pm_runtime_put(dev); |
| |
| return 0; |
| |
| err1: |
| dwc3_gadget_exit(dwc); |
| |
| err0: |
| dwc3_free_event_buffers(dwc); |
| |
| return ret; |
| } |
| |
| static int dwc3_device_intel_remove(struct platform_device *pdev) |
| { |
| iounmap(_dev_data->flis_reg); |
| |
| dwc3_remove(pdev); |
| |
| kfree(_dev_data); |
| _dev_data = NULL; |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM_RUNTIME |
| static const struct dev_pm_ops dwc3_device_pm_ops = { |
| .runtime_suspend = dwc3_runtime_suspend, |
| .runtime_resume = dwc3_runtime_resume, |
| }; |
| #define DWC3_DEVICE_PM_OPS (&dwc3_device_pm_ops) |
| #else |
| #define DWC3_DEVICE_PM_OPS NULL |
| #endif |
| |
| static struct platform_driver dwc3_device_intel_driver = { |
| .probe = dwc3_device_intel_probe, |
| .remove = dwc3_device_intel_remove, |
| .driver = { |
| .name = "dwc3-device", |
| .of_match_table = of_match_ptr(of_dwc3_match), |
| .pm = DWC3_DEVICE_PM_OPS, |
| }, |
| }; |
| |
| module_platform_driver(dwc3_device_intel_driver); |
| |
| MODULE_ALIAS("platform:dwc3"); |
| MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_DESCRIPTION("DesignWare USB3 DRD Controller Driver"); |