Re: [PATCH v1 5/8] usb: chipidea: tegra: Support host mode

Peter Chen
2020-12-16T06:08:55Z

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
>

--

Thanks,
Peter Chen