aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/common/usb_urb.c
blob: be3b6b9f32e845dea3b927de4d1db8f5844cb0b0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// SPDX-License-Identifier: GPL-2.0
/*
 * Common code for usb urb handling, based on the musb-new code
 *
 * Copyright 2021 Linaro, Rui Miguel Silva <rui.silva@linaro.org>
 *
 */

#include <dm/device.h>
#include <dm/device_compat.h>
#include <linux/usb/usb_urb_compat.h>

#include <time.h>
#include <usb.h>

#if CONFIG_IS_ENABLED(DM_USB)
struct usb_device *usb_dev_get_parent(struct usb_device *udev)
{
	struct udevice *parent = udev->dev->parent;

	/*
	 * When called from usb-uclass.c: usb_scan_device() udev->dev points
	 * to the parent udevice, not the actual udevice belonging to the
	 * udev as the device is not instantiated yet.
	 *
	 * If dev is an usb-bus, then we are called from usb_scan_device() for
	 * an usb-device plugged directly into the root port, return NULL.
	 */
	if (device_get_uclass_id(udev->dev) == UCLASS_USB)
		return NULL;

	/*
	 * If these 2 are not the same we are being called from
	 * usb_scan_device() and udev itself is the parent.
	 */
	if (dev_get_parent_priv(udev->dev) != udev)
		return udev;

	/* We are being called normally, use the parent pointer */
	if (device_get_uclass_id(parent) == UCLASS_USB_HUB)
		return dev_get_parent_priv(parent);

	return NULL;
}
#else
struct usb_device *usb_dev_get_parent(struct usb_device *udev)
{
	return udev->parent;
}
#endif

static void usb_urb_complete(struct urb *urb)
{
	urb->dev->status &= ~USB_ST_NOT_PROC;
	urb->dev->act_len = urb->actual_length;

	if (urb->status == -EINPROGRESS)
		urb->status = 0;
}

void usb_urb_fill(struct urb *urb, struct usb_host_endpoint *hep,
		  struct usb_device *dev, int endpoint_type,
		  unsigned long pipe, void *buffer, int len,
		  struct devrequest *setup, int interval)
{
	int epnum = usb_pipeendpoint(pipe);
	int is_in = usb_pipein(pipe);
	u16 maxpacketsize = is_in ? dev->epmaxpacketin[epnum] :
					dev->epmaxpacketout[epnum];

	memset(urb, 0, sizeof(struct urb));
	memset(hep, 0, sizeof(struct usb_host_endpoint));
	INIT_LIST_HEAD(&hep->urb_list);
	INIT_LIST_HEAD(&urb->urb_list);
	urb->ep = hep;
	urb->complete = usb_urb_complete;
	urb->status = -EINPROGRESS;
	urb->dev = dev;
	urb->pipe = pipe;
	urb->transfer_buffer = buffer;
	urb->transfer_dma = (unsigned long)buffer;
	urb->transfer_buffer_length = len;
	urb->setup_packet = (unsigned char *)setup;

	urb->ep->desc.wMaxPacketSize = __cpu_to_le16(maxpacketsize);
	urb->ep->desc.bmAttributes = endpoint_type;
	urb->ep->desc.bEndpointAddress = ((is_in ? USB_DIR_IN : USB_DIR_OUT) |
					  epnum);
	urb->ep->desc.bInterval = interval;
}

int usb_urb_submit(struct usb_hcd *hcd, struct urb *urb)
{
	const struct usb_urb_ops *ops = hcd->urb_ops;
	unsigned long timeout;
	int ret;

	if (!ops)
		return -EINVAL;

	ret = ops->urb_enqueue(hcd, urb, 0);
	if (ret < 0) {
		printf("Failed to enqueue URB to controller\n");
		return ret;
	}

	timeout = get_timer(0) + USB_TIMEOUT_MS(urb->pipe);
	do {
		if (ctrlc())
			return -EIO;
		ops->isr(0, hcd);
	} while (urb->status == -EINPROGRESS && get_timer(0) < timeout);

	if (urb->status == -EINPROGRESS)
		ops->urb_dequeue(hcd, urb, -ETIME);

	return urb->status;
}

int usb_urb_submit_control(struct usb_hcd *hcd, struct urb *urb,
			   struct usb_host_endpoint *hep,
			   struct usb_device *dev, unsigned long pipe,
			   void *buffer, int len, struct devrequest *setup,
			   int interval, enum usb_device_speed speed)
{
	const struct usb_urb_ops *ops = hcd->urb_ops;

	usb_urb_fill(urb, hep, dev, USB_ENDPOINT_XFER_CONTROL, pipe, buffer,
		     len, setup, 0);

	/* Fix speed for non hub-attached devices */
	if (!usb_dev_get_parent(dev)) {
		dev->speed = speed;
		if (ops->hub_control)
			return ops->hub_control(hcd, dev, pipe, buffer, len,
						setup);
	}

	return usb_urb_submit(hcd, urb);
}

int usb_urb_submit_bulk(struct usb_hcd *hcd, struct urb *urb,
			struct usb_host_endpoint *hep, struct usb_device *dev,
			unsigned long pipe, void *buffer, int len)
{
	usb_urb_fill(urb, hep, dev, USB_ENDPOINT_XFER_BULK, pipe, buffer, len,
		     NULL, 0);

	return usb_urb_submit(hcd, urb);
}

int usb_urb_submit_irq(struct usb_hcd *hcd, struct urb *urb,
		       struct usb_host_endpoint *hep, struct usb_device *dev,
		       unsigned long pipe, void *buffer, int len, int interval)
{
	usb_urb_fill(urb, hep, dev, USB_ENDPOINT_XFER_INT, pipe, buffer, len,
		     NULL, interval);

	return usb_urb_submit(hcd, urb);
}