1 // SPDX-License-Identifier: GPL-2.0+
3 * c67x00-hcd.c: Cypress C67X00 USB Host Controller Driver
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.
10 #include <linux/device.h>
11 #include <linux/platform_device.h>
12 #include <linux/usb.h>
15 #include "c67x00-hcd.h"
17 /* --------------------------------------------------------------------------
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 ** */
33 static void c67x00_hub_reset_host_port(struct c67x00_sie
*sie
, int port
)
35 struct c67x00_hcd
*c67x00
= sie
->private_data
;
38 c67x00_ll_husb_reset(sie
, port
);
40 spin_lock_irqsave(&c67x00
->lock
, flags
);
41 c67x00_ll_husb_reset_port(sie
, port
);
42 spin_unlock_irqrestore(&c67x00
->lock
, flags
);
44 c67x00_ll_set_husb_eot(sie
->dev
, DEFAULT_EOT
);
47 static int c67x00_hub_status_data(struct usb_hcd
*hcd
, char *buf
)
49 struct c67x00_hcd
*c67x00
= hcd_to_c67x00_hcd(hcd
);
50 struct c67x00_sie
*sie
= c67x00
->sie
;
55 status
= c67x00_ll_usb_get_status(sie
);
56 for (i
= 0; i
< C67X00_PORTS
; i
++)
57 if (status
& PORT_CONNECT_CHANGE(i
))
60 /* bit 0 denotes hub change, b1..n port change */
66 static int c67x00_hub_control(struct usb_hcd
*hcd
, u16 typeReq
, u16 wValue
,
67 u16 wIndex
, char *buf
, u16 wLength
)
69 struct c67x00_hcd
*c67x00
= hcd_to_c67x00_hcd(hcd
);
70 struct c67x00_sie
*sie
= c67x00
->sie
;
71 u16 status
, usb_status
;
73 unsigned int port
= wIndex
-1;
74 u16 wPortChange
, wPortStatus
;
79 *(__le32
*) buf
= cpu_to_le32(0);
80 len
= 4; /* hub power */
84 if (wIndex
> C67X00_PORTS
)
87 status
= c67x00_ll_usb_get_status(sie
);
88 usb_status
= c67x00_ll_get_usb_ctl(sie
);
91 if (status
& PORT_CONNECT_CHANGE(port
))
92 wPortChange
|= USB_PORT_STAT_C_CONNECTION
;
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
);
101 c67x00
->low_speed_ports
&= ~(1 << port
);
103 if (usb_status
& SOF_EOP_EN(port
))
104 wPortStatus
|= USB_PORT_STAT_ENABLE
;
106 *(__le16
*) buf
= cpu_to_le16(wPortStatus
);
107 *(__le16
*) (buf
+ 2) = cpu_to_le16(wPortChange
);
111 case SetHubFeature
: /* We don't implement these */
112 case ClearHubFeature
:
114 case C_HUB_OVER_CURRENT
:
115 case C_HUB_LOCAL_POWER
:
125 if (wIndex
> C67X00_PORTS
)
129 case USB_PORT_FEAT_SUSPEND
:
130 dev_dbg(c67x00_hcd_dev(c67x00
),
131 "SetPortFeature %d (SUSPEND)\n", port
);
135 case USB_PORT_FEAT_RESET
:
136 c67x00_hub_reset_host_port(sie
, port
);
140 case USB_PORT_FEAT_POWER
:
141 /* Power always enabled */
146 dev_dbg(c67x00_hcd_dev(c67x00
),
147 "%s: SetPortFeature %d (0x%04x) Error!\n",
148 __func__
, port
, wValue
);
153 case ClearPortFeature
:
154 if (wIndex
> C67X00_PORTS
)
158 case USB_PORT_FEAT_ENABLE
:
159 /* Reset the port so that the c67x00 also notices the
161 c67x00_hub_reset_host_port(sie
, port
);
165 case USB_PORT_FEAT_C_ENABLE
:
166 dev_dbg(c67x00_hcd_dev(c67x00
),
167 "ClearPortFeature (%d): C_ENABLE\n", port
);
171 case USB_PORT_FEAT_SUSPEND
:
172 dev_dbg(c67x00_hcd_dev(c67x00
),
173 "ClearPortFeature (%d): SUSPEND\n", port
);
177 case USB_PORT_FEAT_C_SUSPEND
:
178 dev_dbg(c67x00_hcd_dev(c67x00
),
179 "ClearPortFeature (%d): C_SUSPEND\n", port
);
183 case USB_PORT_FEAT_POWER
:
184 dev_dbg(c67x00_hcd_dev(c67x00
),
185 "ClearPortFeature (%d): POWER\n", port
);
188 case USB_PORT_FEAT_C_CONNECTION
:
189 c67x00_ll_usb_clear_status(sie
,
190 PORT_CONNECT_CHANGE(port
));
194 case USB_PORT_FEAT_C_OVER_CURRENT
:
195 dev_dbg(c67x00_hcd_dev(c67x00
),
196 "ClearPortFeature (%d): OVER_CURRENT\n", port
);
200 case USB_PORT_FEAT_C_RESET
:
201 dev_dbg(c67x00_hcd_dev(c67x00
),
202 "ClearPortFeature (%d): C_RESET\n", port
);
207 dev_dbg(c67x00_hcd_dev(c67x00
),
208 "%s: ClearPortFeature %d (0x%04x) Error!\n",
209 __func__
, port
, wValue
);
214 case GetHubDescriptor
:
215 len
= min_t(unsigned int, sizeof(c67x00_hub_des
), wLength
);
216 memcpy(buf
, c67x00_hub_des
, len
);
220 dev_dbg(c67x00_hcd_dev(c67x00
), "%s: unknown\n", __func__
);
227 /* ---------------------------------------------------------------------
228 * Main part of host controller driver
234 * This function is called from the interrupt handler in c67x00-drv.c
236 static void c67x00_hcd_irq(struct c67x00_sie
*sie
, u16 int_status
, u16 msg
)
238 struct c67x00_hcd
*c67x00
= sie
->private_data
;
239 struct usb_hcd
*hcd
= c67x00_hcd_to_hcd(c67x00
);
241 /* Handle sie message flags */
243 if (msg
& HUSB_TDListDone
)
244 c67x00_sched_kick(c67x00
);
246 dev_warn(c67x00_hcd_dev(c67x00
),
247 "Unknown SIE msg flag(s): 0x%04x\n", msg
);
250 if (unlikely(hcd
->state
== HC_STATE_HALT
))
253 if (!HCD_HW_ACCESSIBLE(hcd
))
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
);
264 * c67x00_hcd_start: Host controller start hook
266 static int c67x00_hcd_start(struct usb_hcd
*hcd
)
268 hcd
->uses_new_polling
= 1;
269 hcd
->state
= HC_STATE_RUNNING
;
270 set_bit(HCD_FLAG_POLL_RH
, &hcd
->flags
);
276 * c67x00_hcd_stop: Host controller stop hook
278 static void c67x00_hcd_stop(struct usb_hcd
*hcd
)
283 static int c67x00_hcd_get_frame(struct usb_hcd
*hcd
)
285 struct c67x00_hcd
*c67x00
= hcd_to_c67x00_hcd(hcd
);
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
;
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
,
301 * basic lifecycle operations
303 .start
= c67x00_hcd_start
,
304 .stop
= c67x00_hcd_stop
,
307 * managing i/o requests and associated device resources
309 .urb_enqueue
= c67x00_urb_enqueue
,
310 .urb_dequeue
= c67x00_urb_dequeue
,
311 .endpoint_disable
= c67x00_endpoint_disable
,
316 .get_frame_number
= c67x00_hcd_get_frame
,
321 .hub_status_data
= c67x00_hub_status_data
,
322 .hub_control
= c67x00_hub_control
,
325 /* ---------------------------------------------------------------------
326 * Setup/Teardown routines
329 int c67x00_hcd_probe(struct c67x00_sie
*sie
)
331 struct c67x00_hcd
*c67x00
;
339 hcd
= usb_create_hcd(&c67x00_hc_driver
, sie_dev(sie
), "c67x00_sie");
344 c67x00
= hcd_to_c67x00_hcd(hcd
);
346 spin_lock_init(&c67x00
->lock
);
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
;
359 c67x00_ll_husb_init_host_port(sie
);
361 init_completion(&c67x00
->endpoint_disable
);
362 retval
= c67x00_sched_start_scheduler(c67x00
);
366 retval
= usb_add_hcd(hcd
, 0, 0);
368 dev_dbg(sie_dev(sie
), "%s: usb_add_hcd returned %d\n",
373 device_wakeup_enable(hcd
->self
.controller
);
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
);
383 c67x00_sched_stop_scheduler(c67x00
);
390 /* may be called with controller, bus, and devices active */
391 void c67x00_hcd_remove(struct c67x00_sie
*sie
)
393 struct c67x00_hcd
*c67x00
= sie
->private_data
;
394 struct usb_hcd
*hcd
= c67x00_hcd_to_hcd(c67x00
);
396 c67x00_sched_stop_scheduler(c67x00
);