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