On 20-12-15 23:21:10, Dmitry Osipenko wrote:
> From: Peter Geis <pgwipe...@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
> 

-- 

Thanks,
Peter Chen

Reply via email to