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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [ARM Kernel]     [Linux ARM]     [Linux ARM MSM]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux