xref: /openbmc/linux/drivers/usb/c67x00/c67x00-hcd.c (revision e606c759)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * c67x00-hcd.c: Cypress C67X00 USB Host Controller Driver
4  *
5  * Copyright (C) 2006-2008 Barco N.V.
6  *    Derived from the Cypress cy7c67200/300 ezusb linux driver and
7  *    based on multiple host controller drivers inside the linux kernel.
8  */
9 
10 #include <linux/device.h>
11 #include <linux/platform_device.h>
12 #include <linux/usb.h>
13 
14 #include "c67x00.h"
15 #include "c67x00-hcd.h"
16 
17 /* --------------------------------------------------------------------------
18  * Root Hub Support
19  */
20 
21 static __u8 c67x00_hub_des[] = {
22 	0x09,			/*  __u8  bLength; */
23 	USB_DT_HUB,		/*  __u8  bDescriptorType; Hub-descriptor */
24 	0x02,			/*  __u8  bNbrPorts; */
25 	0x00,			/* __u16  wHubCharacteristics; */
26 	0x00,			/*   (per-port OC, no power switching) */
27 	0x32,			/*  __u8  bPwrOn2pwrGood; 2ms */
28 	0x00,			/*  __u8  bHubContrCurrent; 0 mA */
29 	0x00,			/*  __u8  DeviceRemovable; ** 7 Ports max ** */
30 	0xff,			/*  __u8  PortPwrCtrlMask; ** 7 ports max ** */
31 };
32 
c67x00_hub_reset_host_port(struct c67x00_sie * sie,int port)33 static void c67x00_hub_reset_host_port(struct c67x00_sie *sie, int port)
34 {
35 	struct c67x00_hcd *c67x00 = sie->private_data;
36 	unsigned long flags;
37 
38 	c67x00_ll_husb_reset(sie, port);
39 
40 	spin_lock_irqsave(&c67x00->lock, flags);
41 	c67x00_ll_husb_reset_port(sie, port);
42 	spin_unlock_irqrestore(&c67x00->lock, flags);
43 
44 	c67x00_ll_set_husb_eot(sie->dev, DEFAULT_EOT);
45 }
46 
c67x00_hub_status_data(struct usb_hcd * hcd,char * buf)47 static int c67x00_hub_status_data(struct usb_hcd *hcd, char *buf)
48 {
49 	struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd);
50 	struct c67x00_sie *sie = c67x00->sie;
51 	u16 status;
52 	int i;
53 
54 	*buf = 0;
55 	status = c67x00_ll_usb_get_status(sie);
56 	for (i = 0; i < C67X00_PORTS; i++)
57 		if (status & PORT_CONNECT_CHANGE(i))
58 			*buf |= (1 << i);
59 
60 	/* bit 0 denotes hub change, b1..n port change */
61 	*buf <<= 1;
62 
63 	return !!*buf;
64 }
65 
c67x00_hub_control(struct usb_hcd * hcd,u16 typeReq,u16 wValue,u16 wIndex,char * buf,u16 wLength)66 static int c67x00_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
67 			      u16 wIndex, char *buf, u16 wLength)
68 {
69 	struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd);
70 	struct c67x00_sie *sie = c67x00->sie;
71 	u16 status, usb_status;
72 	int len = 0;
73 	unsigned int port = wIndex-1;
74 	u16 wPortChange, wPortStatus;
75 
76 	switch (typeReq) {
77 
78 	case GetHubStatus:
79 		*(__le32 *) buf = cpu_to_le32(0);
80 		len = 4;		/* hub power */
81 		break;
82 
83 	case GetPortStatus:
84 		if (wIndex > C67X00_PORTS)
85 			return -EPIPE;
86 
87 		status = c67x00_ll_usb_get_status(sie);
88 		usb_status = c67x00_ll_get_usb_ctl(sie);
89 
90 		wPortChange = 0;
91 		if (status & PORT_CONNECT_CHANGE(port))
92 			wPortChange |= USB_PORT_STAT_C_CONNECTION;
93 
94 		wPortStatus = USB_PORT_STAT_POWER;
95 		if (!(status & PORT_SE0_STATUS(port)))
96 			wPortStatus |= USB_PORT_STAT_CONNECTION;
97 		if (usb_status & LOW_SPEED_PORT(port)) {
98 			wPortStatus |= USB_PORT_STAT_LOW_SPEED;
99 			c67x00->low_speed_ports |= (1 << port);
100 		} else
101 			c67x00->low_speed_ports &= ~(1 << port);
102 
103 		if (usb_status & SOF_EOP_EN(port))
104 			wPortStatus |= USB_PORT_STAT_ENABLE;
105 
106 		*(__le16 *) buf = cpu_to_le16(wPortStatus);
107 		*(__le16 *) (buf + 2) = cpu_to_le16(wPortChange);
108 		len = 4;
109 		break;
110 
111 	case SetHubFeature:	/* We don't implement these */
112 	case ClearHubFeature:
113 		switch (wValue) {
114 		case C_HUB_OVER_CURRENT:
115 		case C_HUB_LOCAL_POWER:
116 			len = 0;
117 			break;
118 
119 		default:
120 			return -EPIPE;
121 		}
122 		break;
123 
124 	case SetPortFeature:
125 		if (wIndex > C67X00_PORTS)
126 			return -EPIPE;
127 
128 		switch (wValue) {
129 		case USB_PORT_FEAT_SUSPEND:
130 			dev_dbg(c67x00_hcd_dev(c67x00),
131 				"SetPortFeature %d (SUSPEND)\n", port);
132 			len = 0;
133 			break;
134 
135 		case USB_PORT_FEAT_RESET:
136 			c67x00_hub_reset_host_port(sie, port);
137 			len = 0;
138 			break;
139 
140 		case USB_PORT_FEAT_POWER:
141 			/* Power always enabled */
142 			len = 0;
143 			break;
144 
145 		default:
146 			dev_dbg(c67x00_hcd_dev(c67x00),
147 				"%s: SetPortFeature %d (0x%04x) Error!\n",
148 				__func__, port, wValue);
149 			return -EPIPE;
150 		}
151 		break;
152 
153 	case ClearPortFeature:
154 		if (wIndex > C67X00_PORTS)
155 			return -EPIPE;
156 
157 		switch (wValue) {
158 		case USB_PORT_FEAT_ENABLE:
159 			/* Reset the port so that the c67x00 also notices the
160 			 * disconnect */
161 			c67x00_hub_reset_host_port(sie, port);
162 			len = 0;
163 			break;
164 
165 		case USB_PORT_FEAT_C_ENABLE:
166 			dev_dbg(c67x00_hcd_dev(c67x00),
167 				"ClearPortFeature (%d): C_ENABLE\n", port);
168 			len = 0;
169 			break;
170 
171 		case USB_PORT_FEAT_SUSPEND:
172 			dev_dbg(c67x00_hcd_dev(c67x00),
173 				"ClearPortFeature (%d): SUSPEND\n", port);
174 			len = 0;
175 			break;
176 
177 		case USB_PORT_FEAT_C_SUSPEND:
178 			dev_dbg(c67x00_hcd_dev(c67x00),
179 				"ClearPortFeature (%d): C_SUSPEND\n", port);
180 			len = 0;
181 			break;
182 
183 		case USB_PORT_FEAT_POWER:
184 			dev_dbg(c67x00_hcd_dev(c67x00),
185 				"ClearPortFeature (%d): POWER\n", port);
186 			return -EPIPE;
187 
188 		case USB_PORT_FEAT_C_CONNECTION:
189 			c67x00_ll_usb_clear_status(sie,
190 						   PORT_CONNECT_CHANGE(port));
191 			len = 0;
192 			break;
193 
194 		case USB_PORT_FEAT_C_OVER_CURRENT:
195 			dev_dbg(c67x00_hcd_dev(c67x00),
196 				"ClearPortFeature (%d): OVER_CURRENT\n", port);
197 			len = 0;
198 			break;
199 
200 		case USB_PORT_FEAT_C_RESET:
201 			dev_dbg(c67x00_hcd_dev(c67x00),
202 				"ClearPortFeature (%d): C_RESET\n", port);
203 			len = 0;
204 			break;
205 
206 		default:
207 			dev_dbg(c67x00_hcd_dev(c67x00),
208 				"%s: ClearPortFeature %d (0x%04x) Error!\n",
209 				__func__, port, wValue);
210 			return -EPIPE;
211 		}
212 		break;
213 
214 	case GetHubDescriptor:
215 		len = min_t(unsigned int, sizeof(c67x00_hub_des), wLength);
216 		memcpy(buf, c67x00_hub_des, len);
217 		break;
218 
219 	default:
220 		dev_dbg(c67x00_hcd_dev(c67x00), "%s: unknown\n", __func__);
221 		return -EPIPE;
222 	}
223 
224 	return 0;
225 }
226 
227 /* ---------------------------------------------------------------------
228  * Main part of host controller driver
229  */
230 
231 /*
232  * c67x00_hcd_irq
233  *
234  * This function is called from the interrupt handler in c67x00-drv.c
235  */
c67x00_hcd_irq(struct c67x00_sie * sie,u16 int_status,u16 msg)236 static void c67x00_hcd_irq(struct c67x00_sie *sie, u16 int_status, u16 msg)
237 {
238 	struct c67x00_hcd *c67x00 = sie->private_data;
239 	struct usb_hcd *hcd = c67x00_hcd_to_hcd(c67x00);
240 
241 	/* Handle sie message flags */
242 	if (msg) {
243 		if (msg & HUSB_TDListDone)
244 			c67x00_sched_kick(c67x00);
245 		else
246 			dev_warn(c67x00_hcd_dev(c67x00),
247 				 "Unknown SIE msg flag(s): 0x%04x\n", msg);
248 	}
249 
250 	if (unlikely(hcd->state == HC_STATE_HALT))
251 		return;
252 
253 	if (!HCD_HW_ACCESSIBLE(hcd))
254 		return;
255 
256 	/* Handle Start of frame events */
257 	if (int_status & SOFEOP_FLG(sie->sie_num)) {
258 		c67x00_ll_usb_clear_status(sie, SOF_EOP_IRQ_FLG);
259 		c67x00_sched_kick(c67x00);
260 	}
261 }
262 
263 /*
264  * c67x00_hcd_start: Host controller start hook
265  */
c67x00_hcd_start(struct usb_hcd * hcd)266 static int c67x00_hcd_start(struct usb_hcd *hcd)
267 {
268 	hcd->uses_new_polling = 1;
269 	hcd->state = HC_STATE_RUNNING;
270 	set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
271 
272 	return 0;
273 }
274 
275 /*
276  * c67x00_hcd_stop: Host controller stop hook
277  */
c67x00_hcd_stop(struct usb_hcd * hcd)278 static void c67x00_hcd_stop(struct usb_hcd *hcd)
279 {
280 	/* Nothing to do */
281 }
282 
c67x00_hcd_get_frame(struct usb_hcd * hcd)283 static int c67x00_hcd_get_frame(struct usb_hcd *hcd)
284 {
285 	struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd);
286 	u16 temp_val;
287 
288 	dev_dbg(c67x00_hcd_dev(c67x00), "%s\n", __func__);
289 	temp_val = c67x00_ll_husb_get_frame(c67x00->sie);
290 	temp_val &= HOST_FRAME_MASK;
291 	return temp_val ? (temp_val - 1) : HOST_FRAME_MASK;
292 }
293 
294 static const struct hc_driver c67x00_hc_driver = {
295 	.description	= "c67x00-hcd",
296 	.product_desc	= "Cypress C67X00 Host Controller",
297 	.hcd_priv_size	= sizeof(struct c67x00_hcd),
298 	.flags		= HCD_USB11 | HCD_MEMORY,
299 
300 	/*
301 	 * basic lifecycle operations
302 	 */
303 	.start		= c67x00_hcd_start,
304 	.stop		= c67x00_hcd_stop,
305 
306 	/*
307 	 * managing i/o requests and associated device resources
308 	 */
309 	.urb_enqueue	= c67x00_urb_enqueue,
310 	.urb_dequeue	= c67x00_urb_dequeue,
311 	.endpoint_disable = c67x00_endpoint_disable,
312 
313 	/*
314 	 * scheduling support
315 	 */
316 	.get_frame_number = c67x00_hcd_get_frame,
317 
318 	/*
319 	 * root hub support
320 	 */
321 	.hub_status_data = c67x00_hub_status_data,
322 	.hub_control	= c67x00_hub_control,
323 };
324 
325 /* ---------------------------------------------------------------------
326  * Setup/Teardown routines
327  */
328 
c67x00_hcd_probe(struct c67x00_sie * sie)329 int c67x00_hcd_probe(struct c67x00_sie *sie)
330 {
331 	struct c67x00_hcd *c67x00;
332 	struct usb_hcd *hcd;
333 	unsigned long flags;
334 	int retval;
335 
336 	if (usb_disabled())
337 		return -ENODEV;
338 
339 	hcd = usb_create_hcd(&c67x00_hc_driver, sie_dev(sie), "c67x00_sie");
340 	if (!hcd) {
341 		retval = -ENOMEM;
342 		goto err0;
343 	}
344 	c67x00 = hcd_to_c67x00_hcd(hcd);
345 
346 	spin_lock_init(&c67x00->lock);
347 	c67x00->sie = sie;
348 
349 	INIT_LIST_HEAD(&c67x00->list[PIPE_ISOCHRONOUS]);
350 	INIT_LIST_HEAD(&c67x00->list[PIPE_INTERRUPT]);
351 	INIT_LIST_HEAD(&c67x00->list[PIPE_CONTROL]);
352 	INIT_LIST_HEAD(&c67x00->list[PIPE_BULK]);
353 	c67x00->urb_count = 0;
354 	INIT_LIST_HEAD(&c67x00->td_list);
355 	c67x00->td_base_addr = CY_HCD_BUF_ADDR + SIE_TD_OFFSET(sie->sie_num);
356 	c67x00->buf_base_addr = CY_HCD_BUF_ADDR + SIE_BUF_OFFSET(sie->sie_num);
357 	c67x00->max_frame_bw = MAX_FRAME_BW_STD;
358 
359 	c67x00_ll_husb_init_host_port(sie);
360 
361 	init_completion(&c67x00->endpoint_disable);
362 	retval = c67x00_sched_start_scheduler(c67x00);
363 	if (retval)
364 		goto err1;
365 
366 	retval = usb_add_hcd(hcd, 0, 0);
367 	if (retval) {
368 		dev_dbg(sie_dev(sie), "%s: usb_add_hcd returned %d\n",
369 			__func__, retval);
370 		goto err2;
371 	}
372 
373 	device_wakeup_enable(hcd->self.controller);
374 
375 	spin_lock_irqsave(&sie->lock, flags);
376 	sie->private_data = c67x00;
377 	sie->irq = c67x00_hcd_irq;
378 	spin_unlock_irqrestore(&sie->lock, flags);
379 
380 	return retval;
381 
382  err2:
383 	c67x00_sched_stop_scheduler(c67x00);
384  err1:
385 	usb_put_hcd(hcd);
386  err0:
387 	return retval;
388 }
389 
390 /* may be called with controller, bus, and devices active */
c67x00_hcd_remove(struct c67x00_sie * sie)391 void c67x00_hcd_remove(struct c67x00_sie *sie)
392 {
393 	struct c67x00_hcd *c67x00 = sie->private_data;
394 	struct usb_hcd *hcd = c67x00_hcd_to_hcd(c67x00);
395 
396 	c67x00_sched_stop_scheduler(c67x00);
397 	usb_remove_hcd(hcd);
398 	usb_put_hcd(hcd);
399 }
400