Message ID | 20201215202113.30394-6-digetx@gmail.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Support Runtime PM and host mode by Tegra ChipIdea USB driver | expand |
On 20-12-15 23:21:10, Dmitry Osipenko wrote: > From: Peter Geis <pgwipeout@gmail.com> > > struct tegra_usb_soc_info { > unsigned long flags; > + unsigned int txfifothresh; > + enum usb_dr_mode dr_mode; > +}; > + > +static const struct tegra_usb_soc_info tegra20_ehci_soc_info = { > + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | > + CI_HDRC_OVERRIDE_PHY_CONTROL, > + .dr_mode = USB_DR_MODE_HOST, > + .txfifothresh = 10, > +}; > + > +static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { > + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | > + CI_HDRC_OVERRIDE_PHY_CONTROL > + .dr_mode = USB_DR_MODE_HOST, > + .txfifothresh = 16, > }; > > static const struct tegra_usb_soc_info tegra_udc_soc_info = { > - .flags = CI_HDRC_REQUIRES_ALIGNED_DMA, > + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | > + CI_HDRC_OVERRIDE_PHY_CONTROL, > + .dr_mode = USB_DR_MODE_UNKNOWN, > }; What's the usage for this dr_mode? Does these three SoCs only supports single mode, it usually sets dr_mode at board dts file to indicate USB role for this board. > > static const struct of_device_id tegra_usb_of_match[] = { > { > + .compatible = "nvidia,tegra20-ehci", > + .data = &tegra20_ehci_soc_info, > + }, { > + .compatible = "nvidia,tegra30-ehci", > + .data = &tegra30_ehci_soc_info, > + }, { > .compatible = "nvidia,tegra20-udc", > .data = &tegra_udc_soc_info, Your compatible indicates this controller is single USB role, is it on purpose? > }, { > @@ -47,6 +81,181 @@ static const struct of_device_id tegra_usb_of_match[] = { > }; > MODULE_DEVICE_TABLE(of, tegra_usb_of_match); > > +static int tegra_usb_reset_controller(struct device *dev) > +{ > + struct reset_control *rst, *rst_utmi; > + struct device_node *phy_np; > + int err; > + > + rst = devm_reset_control_get_shared(dev, "usb"); > + if (IS_ERR(rst)) { > + dev_err(dev, "can't get ehci reset: %pe\n", rst); > + return PTR_ERR(rst); > + } > + > + phy_np = of_parse_phandle(dev->of_node, "nvidia,phy", 0); > + if (!phy_np) > + return -ENOENT; > + > + /* > + * The 1st USB controller contains some UTMI pad registers that are > + * global for all the controllers on the chip. Those registers are > + * also cleared when reset is asserted to the 1st controller. > + */ > + rst_utmi = of_reset_control_get_shared(phy_np, "utmi-pads"); > + if (IS_ERR(rst_utmi)) { > + dev_warn(dev, "can't get utmi-pads reset from the PHY\n"); > + dev_warn(dev, "continuing, but please update your DT\n"); > + } else { > + /* > + * PHY driver performs UTMI-pads reset in a case of a > + * non-legacy DT. > + */ > + reset_control_put(rst_utmi); > + } > + > + of_node_put(phy_np); > + > + /* reset control is shared, hence initialize it first */ > + err = reset_control_deassert(rst); > + if (err) > + return err; > + > + err = reset_control_assert(rst); > + if (err) > + return err; > + > + udelay(1); > + > + err = reset_control_deassert(rst); > + if (err) > + return err; > + > + return 0; > +} > + > +static int tegra_usb_notify_event(struct ci_hdrc *ci, unsigned int event) > +{ > + struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent); > + struct ehci_hcd *ehci; > + > + switch (event) { > + case CI_HDRC_CONTROLLER_RESET_EVENT: > + if (ci->hcd) { > + ehci = hcd_to_ehci(ci->hcd); > + ehci->has_tdi_phy_lpm = false; > + ehci_writel(ehci, usb->soc->txfifothresh << 16, > + &ehci->regs->txfill_tuning); > + } > + break; > + } > + > + return 0; > +} > + > +static int tegra_usb_internal_port_reset(struct ehci_hcd *ehci, > + u32 __iomem *portsc_reg, > + unsigned long *flags) > +{ > + u32 saved_usbintr, temp; > + unsigned int i, tries; > + int retval = 0; > + > + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); > + /* disable USB interrupt */ > + ehci_writel(ehci, 0, &ehci->regs->intr_enable); > + spin_unlock_irqrestore(&ehci->lock, *flags); > + > + /* > + * Here we have to do Port Reset at most twice for > + * Port Enable bit to be set. > + */ > + for (i = 0; i < 2; i++) { > + temp = ehci_readl(ehci, portsc_reg); > + temp |= PORT_RESET; > + ehci_writel(ehci, temp, portsc_reg); > + mdelay(10); Needs accurate delay? How about usleep_range? > + temp &= ~PORT_RESET; > + ehci_writel(ehci, temp, portsc_reg); > + mdelay(1); > + tries = 100; > + do { > + mdelay(1); > + /* > + * Up to this point, Port Enable bit is > + * expected to be set after 2 ms waiting. > + * USB1 usually takes extra 45 ms, for safety, > + * we take 100 ms as timeout. > + */ > + temp = ehci_readl(ehci, portsc_reg); > + } while (!(temp & PORT_PE) && tries--); > + if (temp & PORT_PE) > + break; > + } > + if (i == 2) > + retval = -ETIMEDOUT; > + > + /* > + * Clear Connect Status Change bit if it's set. > + * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared. > + */ > + if (temp & PORT_CSC) > + ehci_writel(ehci, PORT_CSC, portsc_reg); > + > + /* > + * Write to clear any interrupt status bits that might be set > + * during port reset. > + */ > + temp = ehci_readl(ehci, &ehci->regs->status); > + ehci_writel(ehci, temp, &ehci->regs->status); > + > + /* restore original interrupt-enable bits */ > + spin_lock_irqsave(&ehci->lock, *flags); > + ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); > + > + return retval; > +} > + > +static int tegra_ehci_hub_control(struct ci_hdrc *ci, u16 typeReq, u16 wValue, > + u16 wIndex, char *buf, u16 wLength, > + bool *done, unsigned long *flags) > +{ > + struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent); > + struct ehci_hcd *ehci = hcd_to_ehci(ci->hcd); > + u32 __iomem *status_reg; > + int retval = 0; > + > + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; > + > + switch (typeReq) { > + case SetPortFeature: > + if (wValue != USB_PORT_FEAT_RESET || !usb->needs_double_reset) > + break; > + > + /* for USB1 port we need to issue Port Reset twice internally */ > + retval = tegra_usb_internal_port_reset(ehci, status_reg, flags); > + *done = true; > + break; > + } > + > + return retval; > +} > + > +static void tegra_usb_enter_lpm(struct ci_hdrc *ci, bool enable) > +{ > + /* > + * Touching any register which belongs to AHB clock domain will > + * hang CPU if USB controller is put into low power mode because > + * AHB USB clock is gated on Tegra in the LPM. > + * > + * Tegra PHY has a separate register for checking the clock status > + * and usb_phy_set_suspend() takes care of gating/ungating the clocks > + * and restoring the PHY state on Tegra. Hence DEVLC/PORTSC registers > + * shouldn't be touched directly by the CI driver. > + */ > + usb_phy_set_suspend(ci->usb_phy, enable); > +} > + > static int tegra_usb_probe(struct platform_device *pdev) > { > const struct tegra_usb_soc_info *soc; > @@ -83,24 +292,49 @@ static int tegra_usb_probe(struct platform_device *pdev) > return err; > } > > + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) > + usb->needs_double_reset = true; > + > + err = tegra_usb_reset_controller(&pdev->dev); > + if (err) { > + dev_err(&pdev->dev, "failed to reset controller: %d\n", err); > + goto fail_power_off; > + } > + > + /* > + * USB controller registers shan't be touched before PHY is %s/shan't/shouldn't > + * initialized, otherwise CPU will hang because clocks are gated. > + * PHY driver controls gating of internal USB clocks on Tegra. > + */ > + err = usb_phy_init(usb->phy); > + if (err) > + goto fail_power_off; > + > + platform_set_drvdata(pdev, usb); > + > /* setup and register ChipIdea HDRC device */ > + usb->soc = soc; > usb->data.name = "tegra-usb"; > usb->data.flags = soc->flags; > usb->data.usb_phy = usb->phy; > + usb->data.dr_mode = soc->dr_mode; > usb->data.capoffset = DEF_CAPOFFSET; > + usb->data.enter_lpm = tegra_usb_enter_lpm; > + usb->data.hub_control = tegra_ehci_hub_control; > + usb->data.notify_event = tegra_usb_notify_event; > > usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource, > pdev->num_resources, &usb->data); > if (IS_ERR(usb->dev)) { > err = PTR_ERR(usb->dev); > dev_err(&pdev->dev, "failed to add HDRC device: %d\n", err); > - goto fail_power_off; > + goto phy_shutdown; > } > > - platform_set_drvdata(pdev, usb); > - > return 0; > > +phy_shutdown: > + usb_phy_shutdown(usb->phy); > fail_power_off: > clk_disable_unprepare(usb->clk); > return err; > @@ -111,6 +345,7 @@ static int tegra_usb_remove(struct platform_device *pdev) > struct tegra_usb *usb = platform_get_drvdata(pdev); > > ci_hdrc_remove_device(usb->dev); > + usb_phy_shutdown(usb->phy); > clk_disable_unprepare(usb->clk); > > return 0; > diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c > index aa40e510b806..3f6c21406dbd 100644 > --- a/drivers/usb/chipidea/core.c > +++ b/drivers/usb/chipidea/core.c > @@ -195,7 +195,7 @@ static void hw_wait_phy_stable(void) > } > > /* The PHY enters/leaves low power mode */ > -static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) > +static void ci_hdrc_enter_lpm_common(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))); > @@ -208,6 +208,11 @@ static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) > 0); > } > > +static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) > +{ > + return ci->platdata->enter_lpm(ci, enable); > +} > + > static int hw_device_init(struct ci_hdrc *ci, void __iomem *base) > { > u32 reg; > @@ -790,6 +795,9 @@ static int ci_get_platdata(struct device *dev, > platdata->pins_device = p; > } > > + if (!platdata->enter_lpm) > + platdata->enter_lpm = ci_hdrc_enter_lpm_common; > + > return 0; > } > > diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c > index 48e4a5ca1835..b8a4c44ab580 100644 > --- a/drivers/usb/chipidea/host.c > +++ b/drivers/usb/chipidea/host.c > @@ -29,6 +29,12 @@ struct ehci_ci_priv { > bool enabled; > }; > > +struct ci_hdrc_dma_aligned_buffer { > + void *kmalloc_ptr; > + void *old_xfer_buffer; > + u8 data[0]; > +}; > + > static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) > { > struct ehci_hcd *ehci = hcd_to_ehci(hcd); > @@ -160,14 +166,14 @@ static int host_start(struct ci_hdrc *ci) > pinctrl_select_state(ci->platdata->pctl, > ci->platdata->pins_host); > > + ci->hcd = hcd; > + > ret = usb_add_hcd(hcd, 0, 0); > if (ret) { > goto disable_reg; > } else { > struct usb_otg *otg = &ci->otg; > > - ci->hcd = hcd; > - Why this changed? Peter > if (ci_otg_is_fsm_mode(ci)) { > otg->host = &hcd->self; > hcd->self.otg_port = 1; > @@ -237,6 +243,7 @@ static int ci_ehci_hub_control( > u32 temp; > unsigned long flags; > int retval = 0; > + bool done = false; > struct device *dev = hcd->self.controller; > struct ci_hdrc *ci = dev_get_drvdata(dev); > > @@ -244,6 +251,13 @@ static int ci_ehci_hub_control( > > spin_lock_irqsave(&ehci->lock, flags); > > + if (ci->platdata->hub_control) { > + retval = ci->platdata->hub_control(ci, typeReq, wValue, wIndex, > + buf, wLength, &done, &flags); > + if (done) > + goto done; > + } > + > if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { > temp = ehci_readl(ehci, status_reg); > if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { > @@ -349,6 +363,86 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) > return 0; > } > > +static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb) > +{ > + struct ci_hdrc_dma_aligned_buffer *temp; > + size_t length; > + > + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) > + return; > + > + temp = container_of(urb->transfer_buffer, > + struct ci_hdrc_dma_aligned_buffer, data); > + > + if (usb_urb_dir_in(urb)) { > + if (usb_pipeisoc(urb->pipe)) > + length = urb->transfer_buffer_length; > + else > + length = urb->actual_length; > + > + memcpy(temp->old_xfer_buffer, temp->data, length); > + } > + urb->transfer_buffer = temp->old_xfer_buffer; > + kfree(temp->kmalloc_ptr); > + > + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; > +} > + > +static int ci_hdrc_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) > +{ > + struct ci_hdrc_dma_aligned_buffer *temp, *kmalloc_ptr; > + const unsigned int ci_hdrc_usb_dma_align = 32; > + size_t kmalloc_size; > + > + if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0 || > + !((uintptr_t)urb->transfer_buffer & (ci_hdrc_usb_dma_align - 1))) > + return 0; > + > + /* Allocate a buffer with enough padding for alignment */ > + kmalloc_size = urb->transfer_buffer_length + > + sizeof(struct ci_hdrc_dma_aligned_buffer) + > + ci_hdrc_usb_dma_align - 1; > + > + kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); > + if (!kmalloc_ptr) > + return -ENOMEM; > + > + /* Position our struct dma_aligned_buffer such that data is aligned */ > + temp = PTR_ALIGN(kmalloc_ptr + 1, ci_hdrc_usb_dma_align) - 1; > + temp->kmalloc_ptr = kmalloc_ptr; > + temp->old_xfer_buffer = urb->transfer_buffer; > + if (usb_urb_dir_out(urb)) > + memcpy(temp->data, urb->transfer_buffer, > + urb->transfer_buffer_length); > + urb->transfer_buffer = temp->data; > + > + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; > + > + return 0; > +} > + > +static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, > + gfp_t mem_flags) > +{ > + int ret; > + > + ret = ci_hdrc_alloc_dma_aligned_buffer(urb, mem_flags); > + if (ret) > + return ret; > + > + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); > + if (ret) > + ci_hdrc_free_dma_aligned_buffer(urb); > + > + return ret; > +} > + > +static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) > +{ > + usb_hcd_unmap_urb_for_dma(hcd, urb); > + ci_hdrc_free_dma_aligned_buffer(urb); > +} > + > int ci_hdrc_host_init(struct ci_hdrc *ci) > { > struct ci_role_driver *rdrv; > @@ -366,6 +460,11 @@ int ci_hdrc_host_init(struct ci_hdrc *ci) > rdrv->name = "host"; > ci->roles[CI_ROLE_HOST] = rdrv; > > + if (ci->platdata->flags & CI_HDRC_REQUIRES_ALIGNED_DMA) { > + ci_ehci_hc_driver.map_urb_for_dma = ci_hdrc_map_urb_for_dma; > + ci_ehci_hc_driver.unmap_urb_for_dma = ci_hdrc_unmap_urb_for_dma; > + } > + > return 0; > } > > diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h > index 025b41687ce9..edf3342507f1 100644 > --- a/include/linux/usb/chipidea.h > +++ b/include/linux/usb/chipidea.h > @@ -88,6 +88,12 @@ struct ci_hdrc_platform_data { > struct pinctrl_state *pins_default; > struct pinctrl_state *pins_host; > struct pinctrl_state *pins_device; > + > + /* platform-specific hooks */ > + int (*hub_control)(struct ci_hdrc *ci, u16 typeReq, u16 wValue, > + u16 wIndex, char *buf, u16 wLength, > + bool *done, unsigned long *flags); > + void (*enter_lpm)(struct ci_hdrc *ci, bool enable); > }; > > /* Default offset of capability registers */ > -- > 2.29.2 >
16.12.2020 09:08, Peter Chen пишет: > On 20-12-15 23:21:10, Dmitry Osipenko wrote: >> From: Peter Geis <pgwipeout@gmail.com> >> >> struct tegra_usb_soc_info { >> unsigned long flags; >> + unsigned int txfifothresh; >> + enum usb_dr_mode dr_mode; >> +}; >> + >> +static const struct tegra_usb_soc_info tegra20_ehci_soc_info = { >> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | >> + CI_HDRC_OVERRIDE_PHY_CONTROL, >> + .dr_mode = USB_DR_MODE_HOST, >> + .txfifothresh = 10, >> +}; >> + >> +static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { >> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | >> + CI_HDRC_OVERRIDE_PHY_CONTROL >> + .dr_mode = USB_DR_MODE_HOST, >> + .txfifothresh = 16, >> }; >> >> static const struct tegra_usb_soc_info tegra_udc_soc_info = { >> - .flags = CI_HDRC_REQUIRES_ALIGNED_DMA, >> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | >> + CI_HDRC_OVERRIDE_PHY_CONTROL, >> + .dr_mode = USB_DR_MODE_UNKNOWN, >> }; > > What's the usage for this dr_mode? Does these three SoCs only supports > single mode, it usually sets dr_mode at board dts file to indicate > USB role for this board. All Tegra SoCs have three USB controllers. Only one of these three controllers supports peripheral / OTG modes, the other two controllers are fixed to the host mode and device trees do not specify the dr_mode for the host controllers. Hence we need to enforce the dr_mode=host for the host-mode controllers. For UDC the default mode is "unknown" because actual mode comes from a board's device tree. >> static const struct of_device_id tegra_usb_of_match[] = { >> { >> + .compatible = "nvidia,tegra20-ehci", >> + .data = &tegra20_ehci_soc_info, >> + }, { >> + .compatible = "nvidia,tegra30-ehci", >> + .data = &tegra30_ehci_soc_info, >> + }, { >> .compatible = "nvidia,tegra20-udc", >> .data = &tegra_udc_soc_info, > > Your compatible indicates this controller is single USB role, is it > on purpose? Yes, the "tegra20/30-ehci" controllers are fixed to the host-mode in hardware. ... >> +static int tegra_usb_internal_port_reset(struct ehci_hcd *ehci, >> + u32 __iomem *portsc_reg, >> + unsigned long *flags) >> +{ >> + u32 saved_usbintr, temp; >> + unsigned int i, tries; >> + int retval = 0; >> + >> + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); >> + /* disable USB interrupt */ >> + ehci_writel(ehci, 0, &ehci->regs->intr_enable); >> + spin_unlock_irqrestore(&ehci->lock, *flags); >> + >> + /* >> + * Here we have to do Port Reset at most twice for >> + * Port Enable bit to be set. >> + */ >> + for (i = 0; i < 2; i++) { >> + temp = ehci_readl(ehci, portsc_reg); >> + temp |= PORT_RESET; >> + ehci_writel(ehci, temp, portsc_reg); >> + mdelay(10); > > Needs accurate delay? How about usleep_range? To be hones I don't know for sure whether hub_control() could be used from interrupt context. This mdelay is borrowed from the older ehci-tegra driver. Are you suggesting that it should be safe to sleep here? ... >> static int tegra_usb_probe(struct platform_device *pdev) >> { >> const struct tegra_usb_soc_info *soc; >> @@ -83,24 +292,49 @@ static int tegra_usb_probe(struct platform_device *pdev) >> return err; >> } >> >> + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) >> + usb->needs_double_reset = true; >> + >> + err = tegra_usb_reset_controller(&pdev->dev); >> + if (err) { >> + dev_err(&pdev->dev, "failed to reset controller: %d\n", err); >> + goto fail_power_off; >> + } >> + >> + /* >> + * USB controller registers shan't be touched before PHY is > > %s/shan't/shouldn't ok ... >> static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) >> { >> struct ehci_hcd *ehci = hcd_to_ehci(hcd); >> @@ -160,14 +166,14 @@ static int host_start(struct ci_hdrc *ci) >> pinctrl_select_state(ci->platdata->pctl, >> ci->platdata->pins_host); >> >> + ci->hcd = hcd; >> + >> ret = usb_add_hcd(hcd, 0, 0); >> if (ret) { >> goto disable_reg; >> } else { >> struct usb_otg *otg = &ci->otg; >> >> - ci->hcd = hcd; >> - > > Why this changed? The ci->hcd is used by tegra_usb_notify_event() to handle reset event and the reset happens once usb_add_hcd() is invoked. Hence we need to pre-assign the hcd pointer, otherwise there will be a NULL dereference.
16.12.2020 12:07, Dmitry Osipenko пишет: >>> static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) >>> { >>> struct ehci_hcd *ehci = hcd_to_ehci(hcd); >>> @@ -160,14 +166,14 @@ static int host_start(struct ci_hdrc *ci) >>> pinctrl_select_state(ci->platdata->pctl, >>> ci->platdata->pins_host); >>> >>> + ci->hcd = hcd; >>> + >>> ret = usb_add_hcd(hcd, 0, 0); >>> if (ret) { >>> goto disable_reg; >>> } else { >>> struct usb_otg *otg = &ci->otg; >>> >>> - ci->hcd = hcd; >>> - >> Why this changed? > The ci->hcd is used by tegra_usb_notify_event() to handle reset event > and the reset happens once usb_add_hcd() is invoked. Hence we need to > pre-assign the hcd pointer, otherwise there will be a NULL dereference. Actually, not a NULL dereference, but the reset will be skipped since we check whether ci->hcd is NULL in the tegra's reset handler.
> >> > >> struct tegra_usb_soc_info { > >> unsigned long flags; > >> + unsigned int txfifothresh; > >> + enum usb_dr_mode dr_mode; > >> +}; > >> + > >> +static const struct tegra_usb_soc_info tegra20_ehci_soc_info = { > >> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | > >> + CI_HDRC_OVERRIDE_PHY_CONTROL, > >> + .dr_mode = USB_DR_MODE_HOST, > >> + .txfifothresh = 10, > >> +}; > >> + > >> +static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { > >> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | > >> + CI_HDRC_OVERRIDE_PHY_CONTROL > >> + .dr_mode = USB_DR_MODE_HOST, > >> + .txfifothresh = 16, > >> }; > >> > >> static const struct tegra_usb_soc_info tegra_udc_soc_info = { > >> - .flags = CI_HDRC_REQUIRES_ALIGNED_DMA, > >> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | > >> + CI_HDRC_OVERRIDE_PHY_CONTROL, > >> + .dr_mode = USB_DR_MODE_UNKNOWN, > >> }; > > > > What's the usage for this dr_mode? Does these three SoCs only supports > > single mode, it usually sets dr_mode at board dts file to indicate USB > > role for this board. > > All Tegra SoCs have three USB controllers. Only one of these three controllers > supports peripheral / OTG modes, the other two controllers are fixed to the > host mode and device trees do not specify the dr_mode for the host > controllers. > > Hence we need to enforce the dr_mode=host for the host-mode controllers. > > For UDC the default mode is "unknown" because actual mode comes from a > board's device tree. > You could add dr_mode property at usb node of soc.dtsi to fulfill that. > >> static const struct of_device_id tegra_usb_of_match[] = { > >> { > >> + .compatible = "nvidia,tegra20-ehci", > >> + .data = &tegra20_ehci_soc_info, > >> + }, { > >> + .compatible = "nvidia,tegra30-ehci", > >> + .data = &tegra30_ehci_soc_info, > >> + }, { > >> .compatible = "nvidia,tegra20-udc", > >> .data = &tegra_udc_soc_info, > > > > Your compatible indicates this controller is single USB role, is it on > > purpose? > > Yes, the "tegra20/30-ehci" controllers are fixed to the host-mode in hardware. > > ... > >> +static int tegra_usb_internal_port_reset(struct ehci_hcd *ehci, > >> + u32 __iomem *portsc_reg, > >> + unsigned long *flags) > >> +{ > >> + u32 saved_usbintr, temp; > >> + unsigned int i, tries; > >> + int retval = 0; > >> + > >> + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); > >> + /* disable USB interrupt */ > >> + ehci_writel(ehci, 0, &ehci->regs->intr_enable); > >> + spin_unlock_irqrestore(&ehci->lock, *flags); > >> + > >> + /* > >> + * Here we have to do Port Reset at most twice for > >> + * Port Enable bit to be set. > >> + */ > >> + for (i = 0; i < 2; i++) { > >> + temp = ehci_readl(ehci, portsc_reg); > >> + temp |= PORT_RESET; > >> + ehci_writel(ehci, temp, portsc_reg); > >> + mdelay(10); > > > > Needs accurate delay? How about usleep_range? > > To be hones I don't know for sure whether hub_control() could be used from > interrupt context. This mdelay is borrowed from the older ehci-tegra driver. > > Are you suggesting that it should be safe to sleep here? > I see msleep is called at tegra_ehci_hub_control at ehci-tegra.c. .hub_control is not called at interrupt context. > ... > >> static int tegra_usb_probe(struct platform_device *pdev) { > >> const struct tegra_usb_soc_info *soc; @@ -83,24 +292,49 @@ > static > >> int tegra_usb_probe(struct platform_device *pdev) > >> return err; > >> } > >> > >> + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) > >> + usb->needs_double_reset = true; > >> + > >> + err = tegra_usb_reset_controller(&pdev->dev); > >> + if (err) { > >> + dev_err(&pdev->dev, "failed to reset controller: %d\n", err); > >> + goto fail_power_off; > >> + } > >> + > >> + /* > >> + * USB controller registers shan't be touched before PHY is > > > > %s/shan't/shouldn't > > ok > > ... > >> static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool > >> enable) { > >> struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -160,14 +166,14 @@ > >> static int host_start(struct ci_hdrc *ci) > >> pinctrl_select_state(ci->platdata->pctl, > >> ci->platdata->pins_host); > >> > >> + ci->hcd = hcd; > >> + > >> ret = usb_add_hcd(hcd, 0, 0); > >> if (ret) { > >> goto disable_reg; > >> } else { > >> struct usb_otg *otg = &ci->otg; > >> > >> - ci->hcd = hcd; > >> - > > > > Why this changed? > > The ci->hcd is used by tegra_usb_notify_event() to handle reset event and the > reset happens once usb_add_hcd() is invoked. Hence we need to pre-assign > the hcd pointer, otherwise there will be a NULL dereference. Get it, please set ci->hcd as NULL at error path. Peter
16.12.2020 12:32, Peter Chen пишет: > >>>> >>>> struct tegra_usb_soc_info { >>>> unsigned long flags; >>>> + unsigned int txfifothresh; >>>> + enum usb_dr_mode dr_mode; >>>> +}; >>>> + >>>> +static const struct tegra_usb_soc_info tegra20_ehci_soc_info = { >>>> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | >>>> + CI_HDRC_OVERRIDE_PHY_CONTROL, >>>> + .dr_mode = USB_DR_MODE_HOST, >>>> + .txfifothresh = 10, >>>> +}; >>>> + >>>> +static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { >>>> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | >>>> + CI_HDRC_OVERRIDE_PHY_CONTROL >>>> + .dr_mode = USB_DR_MODE_HOST, >>>> + .txfifothresh = 16, >>>> }; >>>> >>>> static const struct tegra_usb_soc_info tegra_udc_soc_info = { >>>> - .flags = CI_HDRC_REQUIRES_ALIGNED_DMA, >>>> + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | >>>> + CI_HDRC_OVERRIDE_PHY_CONTROL, >>>> + .dr_mode = USB_DR_MODE_UNKNOWN, >>>> }; >>> >>> What's the usage for this dr_mode? Does these three SoCs only supports >>> single mode, it usually sets dr_mode at board dts file to indicate USB >>> role for this board. >> >> All Tegra SoCs have three USB controllers. Only one of these three controllers >> supports peripheral / OTG modes, the other two controllers are fixed to the >> host mode and device trees do not specify the dr_mode for the host >> controllers. >> >> Hence we need to enforce the dr_mode=host for the host-mode controllers. >> >> For UDC the default mode is "unknown" because actual mode comes from a >> board's device tree. >> > > You could add dr_mode property at usb node of soc.dtsi to fulfill that. This will break older DTBs, we can't do this. ... >>>> static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool >>>> enable) { >>>> struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -160,14 +166,14 @@ >>>> static int host_start(struct ci_hdrc *ci) >>>> pinctrl_select_state(ci->platdata->pctl, >>>> ci->platdata->pins_host); >>>> >>>> + ci->hcd = hcd; >>>> + >>>> ret = usb_add_hcd(hcd, 0, 0); >>>> if (ret) { >>>> goto disable_reg; >>>> } else { >>>> struct usb_otg *otg = &ci->otg; >>>> >>>> - ci->hcd = hcd; >>>> - >>> >>> Why this changed? >> >> The ci->hcd is used by tegra_usb_notify_event() to handle reset event and the >> reset happens once usb_add_hcd() is invoked. Hence we need to pre-assign >> the hcd pointer, otherwise there will be a NULL dereference. > > Get it, please set ci->hcd as NULL at error path. Ok, thanks.
16.12.2020 12:32, Peter Chen пишет: >> ... >>>> +static int tegra_usb_internal_port_reset(struct ehci_hcd *ehci, >>>> + u32 __iomem *portsc_reg, >>>> + unsigned long *flags) >>>> +{ >>>> + u32 saved_usbintr, temp; >>>> + unsigned int i, tries; >>>> + int retval = 0; >>>> + >>>> + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); >>>> + /* disable USB interrupt */ >>>> + ehci_writel(ehci, 0, &ehci->regs->intr_enable); >>>> + spin_unlock_irqrestore(&ehci->lock, *flags); >>>> + >>>> + /* >>>> + * Here we have to do Port Reset at most twice for >>>> + * Port Enable bit to be set. >>>> + */ >>>> + for (i = 0; i < 2; i++) { >>>> + temp = ehci_readl(ehci, portsc_reg); >>>> + temp |= PORT_RESET; >>>> + ehci_writel(ehci, temp, portsc_reg); >>>> + mdelay(10); >>> Needs accurate delay? How about usleep_range? >> To be hones I don't know for sure whether hub_control() could be used from >> interrupt context. This mdelay is borrowed from the older ehci-tegra driver. >> >> Are you suggesting that it should be safe to sleep here? >> > I see msleep is called at tegra_ehci_hub_control at ehci-tegra.c. > .hub_control is not called at interrupt context. > Alright, I'll change to use msleep() in v2, thanks.
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 8685ead6ccc7..661818e8fed6 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -55,7 +55,6 @@ config USB_CHIPIDEA_GENERIC config USB_CHIPIDEA_TEGRA tristate "Enable Tegra USB glue driver" if EMBEDDED depends on OF - depends on USB_CHIPIDEA_UDC default USB_CHIPIDEA endif diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index d8efa80aa1c2..fff130f08996 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -4,11 +4,18 @@ */ #include <linux/clk.h> +#include <linux/io.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/reset.h> +#include <linux/usb.h> #include <linux/usb/chipidea.h> +#include <linux/usb/hcd.h> +#include <linux/usb/of.h> +#include <linux/usb/phy.h> + +#include "../host/ehci.h" #include "ci.h" @@ -16,20 +23,47 @@ struct tegra_usb { struct ci_hdrc_platform_data data; struct platform_device *dev; + const struct tegra_usb_soc_info *soc; struct usb_phy *phy; struct clk *clk; + + bool needs_double_reset; }; struct tegra_usb_soc_info { unsigned long flags; + unsigned int txfifothresh; + enum usb_dr_mode dr_mode; +}; + +static const struct tegra_usb_soc_info tegra20_ehci_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL, + .dr_mode = USB_DR_MODE_HOST, + .txfifothresh = 10, +}; + +static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL + .dr_mode = USB_DR_MODE_HOST, + .txfifothresh = 16, }; static const struct tegra_usb_soc_info tegra_udc_soc_info = { - .flags = CI_HDRC_REQUIRES_ALIGNED_DMA, + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL, + .dr_mode = USB_DR_MODE_UNKNOWN, }; static const struct of_device_id tegra_usb_of_match[] = { { + .compatible = "nvidia,tegra20-ehci", + .data = &tegra20_ehci_soc_info, + }, { + .compatible = "nvidia,tegra30-ehci", + .data = &tegra30_ehci_soc_info, + }, { .compatible = "nvidia,tegra20-udc", .data = &tegra_udc_soc_info, }, { @@ -47,6 +81,181 @@ static const struct of_device_id tegra_usb_of_match[] = { }; MODULE_DEVICE_TABLE(of, tegra_usb_of_match); +static int tegra_usb_reset_controller(struct device *dev) +{ + struct reset_control *rst, *rst_utmi; + struct device_node *phy_np; + int err; + + rst = devm_reset_control_get_shared(dev, "usb"); + if (IS_ERR(rst)) { + dev_err(dev, "can't get ehci reset: %pe\n", rst); + return PTR_ERR(rst); + } + + phy_np = of_parse_phandle(dev->of_node, "nvidia,phy", 0); + if (!phy_np) + return -ENOENT; + + /* + * The 1st USB controller contains some UTMI pad registers that are + * global for all the controllers on the chip. Those registers are + * also cleared when reset is asserted to the 1st controller. + */ + rst_utmi = of_reset_control_get_shared(phy_np, "utmi-pads"); + if (IS_ERR(rst_utmi)) { + dev_warn(dev, "can't get utmi-pads reset from the PHY\n"); + dev_warn(dev, "continuing, but please update your DT\n"); + } else { + /* + * PHY driver performs UTMI-pads reset in a case of a + * non-legacy DT. + */ + reset_control_put(rst_utmi); + } + + of_node_put(phy_np); + + /* reset control is shared, hence initialize it first */ + err = reset_control_deassert(rst); + if (err) + return err; + + err = reset_control_assert(rst); + if (err) + return err; + + udelay(1); + + err = reset_control_deassert(rst); + if (err) + return err; + + return 0; +} + +static int tegra_usb_notify_event(struct ci_hdrc *ci, unsigned int event) +{ + struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent); + struct ehci_hcd *ehci; + + switch (event) { + case CI_HDRC_CONTROLLER_RESET_EVENT: + if (ci->hcd) { + ehci = hcd_to_ehci(ci->hcd); + ehci->has_tdi_phy_lpm = false; + ehci_writel(ehci, usb->soc->txfifothresh << 16, + &ehci->regs->txfill_tuning); + } + break; + } + + return 0; +} + +static int tegra_usb_internal_port_reset(struct ehci_hcd *ehci, + u32 __iomem *portsc_reg, + unsigned long *flags) +{ + u32 saved_usbintr, temp; + unsigned int i, tries; + int retval = 0; + + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); + /* disable USB interrupt */ + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + spin_unlock_irqrestore(&ehci->lock, *flags); + + /* + * Here we have to do Port Reset at most twice for + * Port Enable bit to be set. + */ + for (i = 0; i < 2; i++) { + temp = ehci_readl(ehci, portsc_reg); + temp |= PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + mdelay(10); + temp &= ~PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + mdelay(1); + tries = 100; + do { + mdelay(1); + /* + * Up to this point, Port Enable bit is + * expected to be set after 2 ms waiting. + * USB1 usually takes extra 45 ms, for safety, + * we take 100 ms as timeout. + */ + temp = ehci_readl(ehci, portsc_reg); + } while (!(temp & PORT_PE) && tries--); + if (temp & PORT_PE) + break; + } + if (i == 2) + retval = -ETIMEDOUT; + + /* + * Clear Connect Status Change bit if it's set. + * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared. + */ + if (temp & PORT_CSC) + ehci_writel(ehci, PORT_CSC, portsc_reg); + + /* + * Write to clear any interrupt status bits that might be set + * during port reset. + */ + temp = ehci_readl(ehci, &ehci->regs->status); + ehci_writel(ehci, temp, &ehci->regs->status); + + /* restore original interrupt-enable bits */ + spin_lock_irqsave(&ehci->lock, *flags); + ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); + + return retval; +} + +static int tegra_ehci_hub_control(struct ci_hdrc *ci, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength, + bool *done, unsigned long *flags) +{ + struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent); + struct ehci_hcd *ehci = hcd_to_ehci(ci->hcd); + u32 __iomem *status_reg; + int retval = 0; + + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + + switch (typeReq) { + case SetPortFeature: + if (wValue != USB_PORT_FEAT_RESET || !usb->needs_double_reset) + break; + + /* for USB1 port we need to issue Port Reset twice internally */ + retval = tegra_usb_internal_port_reset(ehci, status_reg, flags); + *done = true; + break; + } + + return retval; +} + +static void tegra_usb_enter_lpm(struct ci_hdrc *ci, bool enable) +{ + /* + * Touching any register which belongs to AHB clock domain will + * hang CPU if USB controller is put into low power mode because + * AHB USB clock is gated on Tegra in the LPM. + * + * Tegra PHY has a separate register for checking the clock status + * and usb_phy_set_suspend() takes care of gating/ungating the clocks + * and restoring the PHY state on Tegra. Hence DEVLC/PORTSC registers + * shouldn't be touched directly by the CI driver. + */ + usb_phy_set_suspend(ci->usb_phy, enable); +} + static int tegra_usb_probe(struct platform_device *pdev) { const struct tegra_usb_soc_info *soc; @@ -83,24 +292,49 @@ static int tegra_usb_probe(struct platform_device *pdev) return err; } + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) + usb->needs_double_reset = true; + + err = tegra_usb_reset_controller(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "failed to reset controller: %d\n", err); + goto fail_power_off; + } + + /* + * USB controller registers shan't be touched before PHY is + * initialized, otherwise CPU will hang because clocks are gated. + * PHY driver controls gating of internal USB clocks on Tegra. + */ + err = usb_phy_init(usb->phy); + if (err) + goto fail_power_off; + + platform_set_drvdata(pdev, usb); + /* setup and register ChipIdea HDRC device */ + usb->soc = soc; usb->data.name = "tegra-usb"; usb->data.flags = soc->flags; usb->data.usb_phy = usb->phy; + usb->data.dr_mode = soc->dr_mode; usb->data.capoffset = DEF_CAPOFFSET; + usb->data.enter_lpm = tegra_usb_enter_lpm; + usb->data.hub_control = tegra_ehci_hub_control; + usb->data.notify_event = tegra_usb_notify_event; usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource, pdev->num_resources, &usb->data); if (IS_ERR(usb->dev)) { err = PTR_ERR(usb->dev); dev_err(&pdev->dev, "failed to add HDRC device: %d\n", err); - goto fail_power_off; + goto phy_shutdown; } - platform_set_drvdata(pdev, usb); - return 0; +phy_shutdown: + usb_phy_shutdown(usb->phy); fail_power_off: clk_disable_unprepare(usb->clk); return err; @@ -111,6 +345,7 @@ static int tegra_usb_remove(struct platform_device *pdev) struct tegra_usb *usb = platform_get_drvdata(pdev); ci_hdrc_remove_device(usb->dev); + usb_phy_shutdown(usb->phy); clk_disable_unprepare(usb->clk); return 0; diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index aa40e510b806..3f6c21406dbd 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -195,7 +195,7 @@ static void hw_wait_phy_stable(void) } /* The PHY enters/leaves low power mode */ -static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) +static void ci_hdrc_enter_lpm_common(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))); @@ -208,6 +208,11 @@ static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) 0); } +static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) +{ + return ci->platdata->enter_lpm(ci, enable); +} + static int hw_device_init(struct ci_hdrc *ci, void __iomem *base) { u32 reg; @@ -790,6 +795,9 @@ static int ci_get_platdata(struct device *dev, platdata->pins_device = p; } + if (!platdata->enter_lpm) + platdata->enter_lpm = ci_hdrc_enter_lpm_common; + return 0; } diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 48e4a5ca1835..b8a4c44ab580 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -29,6 +29,12 @@ struct ehci_ci_priv { bool enabled; }; +struct ci_hdrc_dma_aligned_buffer { + void *kmalloc_ptr; + void *old_xfer_buffer; + u8 data[0]; +}; + static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -160,14 +166,14 @@ static int host_start(struct ci_hdrc *ci) pinctrl_select_state(ci->platdata->pctl, ci->platdata->pins_host); + ci->hcd = hcd; + ret = usb_add_hcd(hcd, 0, 0); if (ret) { goto disable_reg; } else { struct usb_otg *otg = &ci->otg; - ci->hcd = hcd; - if (ci_otg_is_fsm_mode(ci)) { otg->host = &hcd->self; hcd->self.otg_port = 1; @@ -237,6 +243,7 @@ static int ci_ehci_hub_control( u32 temp; unsigned long flags; int retval = 0; + bool done = false; struct device *dev = hcd->self.controller; struct ci_hdrc *ci = dev_get_drvdata(dev); @@ -244,6 +251,13 @@ static int ci_ehci_hub_control( spin_lock_irqsave(&ehci->lock, flags); + if (ci->platdata->hub_control) { + retval = ci->platdata->hub_control(ci, typeReq, wValue, wIndex, + buf, wLength, &done, &flags); + if (done) + goto done; + } + if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { temp = ehci_readl(ehci, status_reg); if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { @@ -349,6 +363,86 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) return 0; } +static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb) +{ + struct ci_hdrc_dma_aligned_buffer *temp; + size_t length; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + temp = container_of(urb->transfer_buffer, + struct ci_hdrc_dma_aligned_buffer, data); + + if (usb_urb_dir_in(urb)) { + if (usb_pipeisoc(urb->pipe)) + length = urb->transfer_buffer_length; + else + length = urb->actual_length; + + memcpy(temp->old_xfer_buffer, temp->data, length); + } + urb->transfer_buffer = temp->old_xfer_buffer; + kfree(temp->kmalloc_ptr); + + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; +} + +static int ci_hdrc_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) +{ + struct ci_hdrc_dma_aligned_buffer *temp, *kmalloc_ptr; + const unsigned int ci_hdrc_usb_dma_align = 32; + size_t kmalloc_size; + + if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (ci_hdrc_usb_dma_align - 1))) + return 0; + + /* Allocate a buffer with enough padding for alignment */ + kmalloc_size = urb->transfer_buffer_length + + sizeof(struct ci_hdrc_dma_aligned_buffer) + + ci_hdrc_usb_dma_align - 1; + + kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); + if (!kmalloc_ptr) + return -ENOMEM; + + /* Position our struct dma_aligned_buffer such that data is aligned */ + temp = PTR_ALIGN(kmalloc_ptr + 1, ci_hdrc_usb_dma_align) - 1; + temp->kmalloc_ptr = kmalloc_ptr; + temp->old_xfer_buffer = urb->transfer_buffer; + if (usb_urb_dir_out(urb)) + memcpy(temp->data, urb->transfer_buffer, + urb->transfer_buffer_length); + urb->transfer_buffer = temp->data; + + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; +} + +static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + int ret; + + ret = ci_hdrc_alloc_dma_aligned_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + ci_hdrc_free_dma_aligned_buffer(urb); + + return ret; +} + +static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + ci_hdrc_free_dma_aligned_buffer(urb); +} + int ci_hdrc_host_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; @@ -366,6 +460,11 @@ int ci_hdrc_host_init(struct ci_hdrc *ci) rdrv->name = "host"; ci->roles[CI_ROLE_HOST] = rdrv; + if (ci->platdata->flags & CI_HDRC_REQUIRES_ALIGNED_DMA) { + ci_ehci_hc_driver.map_urb_for_dma = ci_hdrc_map_urb_for_dma; + ci_ehci_hc_driver.unmap_urb_for_dma = ci_hdrc_unmap_urb_for_dma; + } + return 0; } diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index 025b41687ce9..edf3342507f1 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -88,6 +88,12 @@ struct ci_hdrc_platform_data { struct pinctrl_state *pins_default; struct pinctrl_state *pins_host; struct pinctrl_state *pins_device; + + /* platform-specific hooks */ + int (*hub_control)(struct ci_hdrc *ci, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength, + bool *done, unsigned long *flags); + void (*enter_lpm)(struct ci_hdrc *ci, bool enable); }; /* Default offset of capability registers */