diff mbox series

[4/5] usb: chipidea: tegra: add the tegra specific bits

Message ID 20191002014153.29831-5-pgwipeout@gmail.com
State Deferred
Headers show
Series enable tegra-udc host mode | expand

Commit Message

Peter Geis Oct. 2, 2019, 1:41 a.m. UTC
As Tegra requires special handlers for resets and dma alignment, add
those functions to a header.

Signed-off-by: Peter Geis <pgwipeout@gmail.com>
---
 drivers/usb/chipidea/tegra.h | 159 +++++++++++++++++++++++++++++++++++
 1 file changed, 159 insertions(+)
 create mode 100644 drivers/usb/chipidea/tegra.h

Comments

Thierry Reding Oct. 2, 2019, 11:26 a.m. UTC | #1
On Tue, Oct 01, 2019 at 09:41:52PM -0400, Peter Geis wrote:
> As Tegra requires special handlers for resets and dma alignment, add
> those functions to a header.
> 
> Signed-off-by: Peter Geis <pgwipeout@gmail.com>
> ---
>  drivers/usb/chipidea/tegra.h | 159 +++++++++++++++++++++++++++++++++++
>  1 file changed, 159 insertions(+)
>  create mode 100644 drivers/usb/chipidea/tegra.h

Can't you move these into the ci_hdrc_tegra.c file? It's kind of odd to
have large functions like this implemented in a header.

Thierry
Peter Geis Oct. 2, 2019, 12:07 p.m. UTC | #2
On Wed, Oct 2, 2019 at 7:26 AM Thierry Reding <thierry.reding@gmail.com> wrote:
>
> On Tue, Oct 01, 2019 at 09:41:52PM -0400, Peter Geis wrote:
> > As Tegra requires special handlers for resets and dma alignment, add
> > those functions to a header.
> >
> > Signed-off-by: Peter Geis <pgwipeout@gmail.com>
> > ---
> >  drivers/usb/chipidea/tegra.h | 159 +++++++++++++++++++++++++++++++++++
> >  1 file changed, 159 insertions(+)
> >  create mode 100644 drivers/usb/chipidea/tegra.h
>
> Can't you move these into the ci_hdrc_tegra.c file? It's kind of odd to
> have large functions like this implemented in a header.
>
> Thierry

I put this into a header since the chipidea host driver uses it
exclusively and it doesn't export most of its functions.
The other ways involved significant changes to the host driver or
duplicating a lot of functionality in the tegra-udc driver.
Neither method seemed good to me, and I couldn't figure out a better
way to do it.
Dmitry Osipenko Oct. 2, 2019, 4:34 p.m. UTC | #3
02.10.2019 15:07, Peter Geis пишет:
> On Wed, Oct 2, 2019 at 7:26 AM Thierry Reding <thierry.reding@gmail.com> wrote:
>>
>> On Tue, Oct 01, 2019 at 09:41:52PM -0400, Peter Geis wrote:
>>> As Tegra requires special handlers for resets and dma alignment, add
>>> those functions to a header.
>>>
>>> Signed-off-by: Peter Geis <pgwipeout@gmail.com>
>>> ---
>>>  drivers/usb/chipidea/tegra.h | 159 +++++++++++++++++++++++++++++++++++
>>>  1 file changed, 159 insertions(+)
>>>  create mode 100644 drivers/usb/chipidea/tegra.h
>>
>> Can't you move these into the ci_hdrc_tegra.c file? It's kind of odd to
>> have large functions like this implemented in a header.
>>
>> Thierry
> 
> I put this into a header since the chipidea host driver uses it
> exclusively and it doesn't export most of its functions.
> The other ways involved significant changes to the host driver or
> duplicating a lot of functionality in the tegra-udc driver.
> Neither method seemed good to me, and I couldn't figure out a better
> way to do it.
> 

Looks like you could add hooks for [un]map_urb_for_dma() and port_reset() to the
ci_hdrc_platform_data.

Then it could be:

...
ci_ehci_hc_driver.map_urb_for_dma = ci->platdata->map_urb_for_dma;
ci_ehci_hc_driver.unmap_urb_for_dma = ci->platdata->unmap_urb_for_dma;
...
if (ci->platdata->port_reset &&
    typeReq == SetPortFeature && wValue == USB_PORT_FEAT_RESET) {
	spin_unlock_irqrestore(&ehci->lock, flags);
	return ci->platdata->port_reset(ehci, status_reg);
}

and in tegra_udc_probe():

/* setup and register ChipIdea HDRC device */
...
udc->data.map_urb_for_dma = tegra_map_urb_for_dma;
udc->data.unmap_urb_for_dma = tegra_unmap_urb_for_dma;
udc->data.port_reset = tegra_ehci_internal_port_reset;
diff mbox series

Patch

diff --git a/drivers/usb/chipidea/tegra.h b/drivers/usb/chipidea/tegra.h
new file mode 100644
index 000000000000..4e61381bbd68
--- /dev/null
+++ b/drivers/usb/chipidea/tegra.h
@@ -0,0 +1,159 @@ 
+/* SPDX-License-Identifier: GPL-2.0+
+ *
+ * Tegra specific bits for Chipidea USB dual role driver
+ *
+ * Pulled from ehci-tegra.c
+ *
+ */
+
+#define TEGRA_USB_DMA_ALIGN 32
+
+struct tegra_dma_aligned_buffer {
+	void *kmalloc_ptr;
+	void *old_xfer_buffer;
+	u8 data[0];
+};
+
+static int tegra_ehci_internal_port_reset(
+	struct ehci_hcd *ehci,
+	u32 __iomem     *portsc_reg
+)
+{
+	u32             temp;
+	unsigned long   flags;
+	int             retval = 0;
+	int             i, tries;
+	u32             saved_usbintr;
+
+	spin_lock_irqsave(&ehci->lock, flags);
+	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 */
+	ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable);
+	return retval;
+}
+
+static void tegra_free_dma_aligned_buffer(struct urb *urb)
+{
+	struct tegra_dma_aligned_buffer *temp;
+	size_t length;
+
+	if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
+		return;
+
+	temp = container_of(urb->transfer_buffer,
+		struct tegra_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 tegra_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
+{
+	struct tegra_dma_aligned_buffer *temp, *kmalloc_ptr;
+	size_t kmalloc_size;
+
+	if (urb->num_sgs || urb->sg ||
+		urb->transfer_buffer_length == 0 ||
+		!((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1)))
+		return 0;
+
+	/* Allocate a buffer with enough padding for alignment */
+	kmalloc_size = urb->transfer_buffer_length +
+		sizeof(struct tegra_dma_aligned_buffer) + TEGRA_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, TEGRA_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 tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
+				      gfp_t mem_flags)
+{
+	int ret;
+
+	ret = tegra_alloc_dma_aligned_buffer(urb, mem_flags);
+	if (ret)
+		return ret;
+
+	ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
+	if (ret)
+		tegra_free_dma_aligned_buffer(urb);
+
+	return ret;
+}
+
+static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
+{
+	usb_hcd_unmap_urb_for_dma(hcd, urb);
+	tegra_free_dma_aligned_buffer(urb);
+}