diff options
Diffstat (limited to 'target/linux/adm5120/files/drivers')
7 files changed, 4259 insertions, 835 deletions
diff --git a/target/linux/adm5120/files/drivers/usb/host/adm5120-dbg.c b/target/linux/adm5120/files/drivers/usb/host/adm5120-dbg.c new file mode 100644 index 000000000..817d97548 --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-dbg.c @@ -0,0 +1,676 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * + * This file is licenced under the GPL. + */ + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG + +#define edstring(ed_type) ({ char *temp; \ + switch (ed_type) { \ + case PIPE_CONTROL: temp = "ctrl"; break; \ + case PIPE_BULK: temp = "bulk"; break; \ + case PIPE_INTERRUPT: temp = "intr"; break; \ + default: temp = "isoc"; break; \ + }; temp;}) +#define pipestring(pipe) edstring(usb_pipetype(pipe)) + +/* debug| print the main components of an URB + * small: 0) header + data packets 1) just header + */ +static void __attribute__((unused)) +urb_print(struct urb * urb, char * str, int small) +{ + unsigned int pipe= urb->pipe; + + if (!urb->dev || !urb->dev->bus) { + dbg("%s URB: no dev", str); + return; + } + +#ifndef ADMHC_VERBOSE_DEBUG + if (urb->status != 0) +#endif + dbg("%s %p dev=%d ep=%d%s-%s flags=%x len=%d/%d stat=%d", + str, + urb, + usb_pipedevice (pipe), + usb_pipeendpoint (pipe), + usb_pipeout(pipe)? "out" : "in", + pipestring(pipe), + urb->transfer_flags, + urb->actual_length, + urb->transfer_buffer_length, + urb->status); + +#ifdef ADMHC_VERBOSE_DEBUG + if (!small) { + int i, len; + + if (usb_pipecontrol(pipe)) { + printk(KERN_DEBUG __FILE__ ": setup(8):"); + for (i = 0; i < 8 ; i++) + printk (" %02x", ((__u8 *) urb->setup_packet) [i]); + printk ("\n"); + } + if (urb->transfer_buffer_length > 0 && urb->transfer_buffer) { + printk(KERN_DEBUG __FILE__ ": data(%d/%d):", + urb->actual_length, + urb->transfer_buffer_length); + len = usb_pipeout(pipe)? + urb->transfer_buffer_length: urb->actual_length; + for (i = 0; i < 16 && i < len; i++) + printk (" %02x", ((__u8 *) urb->transfer_buffer) [i]); + printk ("%s stat:%d\n", i < len? "...": "", urb->status); + } + } +#endif /* ADMHC_VERBOSE_DEBUG */ +} + +#define admhc_dbg_sw(ahcd, next, size, format, arg...) \ + do { \ + if (next) { \ + unsigned s_len; \ + s_len = scnprintf(*next, *size, format, ## arg ); \ + *size -= s_len; *next += s_len; \ + } else \ + admhc_dbg(ahcd,format, ## arg ); \ + } while (0); + + +static void admhc_dump_intr_mask ( + struct admhcd *ahcd, + char *label, + u32 mask, + char **next, + unsigned *size) +{ + admhc_dbg_sw(ahcd, next, size, "%s 0x%08x%s%s%s%s%s%s%s%s%s%s\n", + label, + mask, + (mask & ADMHC_INTR_INTA) ? " INTA" : "", + (mask & ADMHC_INTR_FATI) ? " FATI" : "", + (mask & ADMHC_INTR_SWI) ? " SWI" : "", + (mask & ADMHC_INTR_TDC) ? " TDC" : "", + (mask & ADMHC_INTR_FNO) ? " FNO" : "", + (mask & ADMHC_INTR_SO) ? " SO" : "", + (mask & ADMHC_INTR_INSM) ? " INSM" : "", + (mask & ADMHC_INTR_BABI) ? " BABI" : "", + (mask & ADMHC_INTR_RESI) ? " RESI" : "", + (mask & ADMHC_INTR_SOFI) ? " SOFI" : "" + ); +} + +static void maybe_print_eds ( + struct admhcd *ahcd, + char *label, + u32 value, + char **next, + unsigned *size) +{ + if (value) + admhc_dbg_sw(ahcd, next, size, "%s %08x\n", label, value); +} + +static char *buss2string (int state) +{ + switch (state) { + case ADMHC_BUSS_RESET: + return "reset"; + case ADMHC_BUSS_RESUME: + return "resume"; + case ADMHC_BUSS_OPER: + return "operational"; + case ADMHC_BUSS_SUSPEND: + return "suspend"; + } + return "(bad state)"; +} + +static void +admhc_dump_status(struct admhcd *ahcd, char **next, unsigned *size) +{ + struct admhcd_regs __iomem *regs = ahcd->regs; + u32 temp; + + temp = admhc_readl(ahcd, ®s->gencontrol); + admhc_dbg_sw(ahcd, next, size, + "gencontrol 0x%08x%s%s%s%s\n", + temp, + (temp & ADMHC_CTRL_UHFE) ? " UHFE" : "", + (temp & ADMHC_CTRL_SIR) ? " SIR" : "", + (temp & ADMHC_CTRL_DMAA) ? " DMAA" : "", + (temp & ADMHC_CTRL_SR) ? " SR" : "" + ); + + temp = admhc_readl(ahcd, ®s->host_control); + admhc_dbg_sw(ahcd, next, size, + "host_control 0x%08x BUSS=%s%s\n", + temp, + buss2string (temp & ADMHC_HC_BUSS), + (temp & ADMHC_HC_DMAE) ? " DMAE" : "" + ); + + admhc_dump_intr_mask(ahcd, "int_status", + admhc_readl(ahcd, ®s->int_status), + next, size); + admhc_dump_intr_mask(ahcd, "int_enable", + admhc_readl(ahcd, ®s->int_enable), + next, size); + + maybe_print_eds(ahcd, "hosthead", + admhc_readl(ahcd, ®s->hosthead), next, size); +} + +#define dbg_port_sw(hc,num,value,next,size) \ + admhc_dbg_sw(hc, next, size, \ + "portstatus [%d] " \ + "0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \ + num, temp, \ + (temp & ADMHC_PS_PRSC) ? " PRSC" : "", \ + (temp & ADMHC_PS_OCIC) ? " OCIC" : "", \ + (temp & ADMHC_PS_PSSC) ? " PSSC" : "", \ + (temp & ADMHC_PS_PESC) ? " PESC" : "", \ + (temp & ADMHC_PS_CSC) ? " CSC" : "", \ + \ + (temp & ADMHC_PS_LSDA) ? " LSDA" : "", \ + (temp & ADMHC_PS_PPS) ? " PPS" : "", \ + (temp & ADMHC_PS_PRS) ? " PRS" : "", \ + (temp & ADMHC_PS_POCI) ? " POCI" : "", \ + (temp & ADMHC_PS_PSS) ? " PSS" : "", \ + \ + (temp & ADMHC_PS_PES) ? " PES" : "", \ + (temp & ADMHC_PS_CCS) ? " CCS" : "" \ + ); + + +static void +admhc_dump_roothub( + struct admhcd *ahcd, + int verbose, + char **next, + unsigned *size) +{ + u32 temp, i; + + temp = admhc_get_rhdesc(ahcd); + if (temp == ~(u32)0) + return; + + if (verbose) { + admhc_dbg_sw(ahcd, next, size, + "rhdesc %08x%s%s%s%s%s%s PPCM=%02x%s%s%s%s NUMP=%d(%d)\n", + temp, + (temp & ADMHC_RH_CRWE) ? " CRWE" : "", + (temp & ADMHC_RH_OCIC) ? " OCIC" : "", + (temp & ADMHC_RH_LPSC) ? " LPSC" : "", + (temp & ADMHC_RH_LPSC) ? " DRWE" : "", + (temp & ADMHC_RH_LPSC) ? " OCI" : "", + (temp & ADMHC_RH_LPSC) ? " LPS" : "", + ((temp & ADMHC_RH_PPCM) >> 16), + (temp & ADMHC_RH_NOCP) ? " NOCP" : "", + (temp & ADMHC_RH_OCPM) ? " OCPM" : "", + (temp & ADMHC_RH_NPS) ? " NPS" : "", + (temp & ADMHC_RH_PSM) ? " PSM" : "", + (temp & ADMHC_RH_NUMP), ahcd->num_ports + ); + } + + for (i = 0; i < ahcd->num_ports; i++) { + temp = admhc_get_portstatus(ahcd, i); + dbg_port_sw(ahcd, i, temp, next, size); + } +} + +static void admhc_dump(struct admhcd *ahcd, int verbose) +{ + admhc_dbg(ahcd, "ADMHC ahcd state\n"); + + /* dumps some of the state we know about */ + admhc_dump_status(ahcd, NULL, NULL); + admhc_dbg(ahcd,"current frame #%04x\n", + admhc_frame_no(ahcd)); + + admhc_dump_roothub(ahcd, verbose, NULL, NULL); +} + +static const char data0[] = "DATA0"; +static const char data1[] = "DATA1"; + +static void admhc_dump_td(const struct admhcd *ahcd, const char *label, + const struct td *td) +{ + u32 tmp = hc32_to_cpup(ahcd, &td->hwINFO); + + admhc_dbg(ahcd, "%s td %p; urb %p index %d; hwNextTD %08x\n", + label, td, + td->urb, td->index, + hc32_to_cpup(ahcd, &td->hwNextTD)); + + if ((td->flags & TD_FLAG_ISO) == 0) { + const char *toggle, *pid; + + switch (tmp & TD_T) { + case TD_T_DATA0: toggle = data0; break; + case TD_T_DATA1: toggle = data1; break; + case TD_T_CARRY: toggle = "CARRY"; break; + default: toggle = "(bad toggle)"; break; + } + switch (tmp & TD_DP) { + case TD_DP_SETUP: pid = "SETUP"; break; + case TD_DP_IN: pid = "IN"; break; + case TD_DP_OUT: pid = "OUT"; break; + default: pid = "(bad pid)"; break; + } + admhc_dbg(ahcd, + " status %08x%s CC=%x EC=%d %s %s ISI=%x FN=%x\n", + tmp, + (tmp & TD_OWN) ? " OWN" : "", + TD_CC_GET(tmp), + TD_EC_GET(tmp), + toggle, + pid, + TD_ISI_GET(tmp), + TD_FN_GET(tmp)); + } else { +#if 0 /* TODO: remove */ + unsigned i; + admhc_dbg(ahcd, " info %08x CC=%x FC=%d DI=%d SF=%04x\n", tmp, + TD_CC_GET(tmp), + (tmp >> 24) & 0x07, + (tmp & TD_DI) >> 21, + tmp & 0x0000ffff); + admhc_dbg(ahcd, " bp0 %08x be %08x\n", + hc32_to_cpup (ahcd, &td->hwCBP) & ~0x0fff, + hc32_to_cpup (ahcd, &td->hwBE)); +#endif + } + + tmp = hc32_to_cpup(ahcd, &td->hwCBL); + admhc_dbg(ahcd, " dbp %08x; cbl %08x; LEN=%d%s\n", + hc32_to_cpup (ahcd, &td->hwDBP), + tmp, + TD_BL_GET(tmp), + (tmp & TD_IE) ? " IE" : "" + ); +} + +/* caller MUST own hcd spinlock if verbose is set! */ +static void __attribute__((unused)) +admhc_dump_ed(const struct admhcd *ahcd, const char *label, + const struct ed *ed, int verbose) +{ + u32 tmp = hc32_to_cpu(ahcd, ed->hwINFO); + + admhc_dbg(ahcd, "%s ed %p state 0x%x type %s; next ed %08x\n", + label, + ed, ed->state, edstring (ed->type), + hc32_to_cpup (ahcd, &ed->hwNextED)); + + admhc_dbg(ahcd, " info %08x MAX=%d%s%s%s%s EP=%d DEV=%d\n", tmp, + ED_MPS_GET(tmp), + (tmp & ED_ISO) ? " ISO" : "", + (tmp & ED_SKIP) ? " SKIP" : "", + (tmp & ED_SPEED_FULL) ? " FULL" : " LOW", + (tmp & ED_INT) ? " INT" : "", + ED_EN_GET(tmp), + ED_FA_GET(tmp)); + + tmp = hc32_to_cpup(ahcd, &ed->hwHeadP); + admhc_dbg(ahcd, " tds: head %08x tail %08x %s%s%s\n", + tmp & TD_MASK, + hc32_to_cpup (ahcd, &ed->hwTailP), + (tmp & ED_C) ? data1 : data0, + (tmp & ED_H) ? " HALT" : "", + verbose ? " td list follows" : " (not listing)"); + + if (verbose) { + struct list_head *tmp; + + /* use ed->td_list because HC concurrently modifies + * hwNextTD as it accumulates ed_donelist. + */ + list_for_each(tmp, &ed->td_list) { + struct td *td; + td = list_entry(tmp, struct td, td_list); + admhc_dump_td (ahcd, " ->", td); + } + } +} + +#else /* ifdef DEBUG */ + +static inline void urb_print(struct urb * urb, char * str, int small) {} +static inline void admhc_dump_ed(const struct admhcd *ahcd, const char *label, + const struct ed *ed, int verbose) {} +static inline void admhc_dump(struct admhcd *ahcd, int verbose) {} + +#undef ADMHC_VERBOSE_DEBUG + +#endif /* DEBUG */ + +/*-------------------------------------------------------------------------*/ + +#ifdef STUB_DEBUG_FILES + +static inline void create_debug_files(struct admhcd *bus) { } +static inline void remove_debug_files(struct admhcd *bus) { } + +#else + +static ssize_t +show_list(struct admhcd *ahcd, char *buf, size_t count, struct ed *ed) +{ + unsigned temp, size = count; + + if (!ed) + return 0; + +#if 0 + /* print first --> last */ + while (ed->ed_prev) + ed = ed->ed_prev; +#endif + + /* dump a snapshot of the bulk or control schedule */ + while (ed) { + u32 info = hc32_to_cpu(ahcd, ed->hwINFO); + u32 headp = hc32_to_cpu(ahcd, ed->hwHeadP); + struct list_head *entry; + struct td *td; + + temp = scnprintf(buf, size, + "ed/%p %s %cs dev%d ep%d %s%smax %d %08x%s%s %s", + ed, + edstring (ed->type), + (info & ED_SPEED_FULL) ? 'f' : 'l', + info & ED_FA_MASK, + (info >> ED_EN_SHIFT) & ED_EN_MASK, + (info & ED_INT) ? "INT " : "", + (info & ED_ISO) ? "ISO " : "", + (info >> ED_MPS_SHIFT) & ED_MPS_MASK , + info, + (info & ED_SKIP) ? " S" : "", + (headp & ED_H) ? " H" : "", + (headp & ED_C) ? data1 : data0); + size -= temp; + buf += temp; + + list_for_each(entry, &ed->td_list) { + u32 dbp, cbl; + + td = list_entry(entry, struct td, td_list); + info = hc32_to_cpup (ahcd, &td->hwINFO); + dbp = hc32_to_cpup (ahcd, &td->hwDBP); + cbl = hc32_to_cpup (ahcd, &td->hwCBL); + + temp = scnprintf(buf, size, + "\n\ttd %p %s %d %s%scc=%x urb %p (%08x,%08x)", + td, + ({ char *pid; + switch (info & TD_DP) { + case TD_DP_SETUP: pid = "setup"; break; + case TD_DP_IN: pid = "in"; break; + case TD_DP_OUT: pid = "out"; break; + default: pid = "(bad pid)"; break; + } pid;}), + TD_BL_GET(cbl), + (info & TD_OWN) ? "" : "DONE ", + (cbl & TD_IE) ? "IE " : "", + TD_CC_GET (info), td->urb, info, cbl); + size -= temp; + buf += temp; + } + + temp = scnprintf(buf, size, "\n"); + size -= temp; + buf += temp; + + ed = ed->ed_next; + } + return count - size; +} + +static ssize_t +show_async(struct class_device *class_dev, char *buf) +{ + struct usb_bus *bus; + struct usb_hcd *hcd; + struct admhcd *ahcd; + size_t temp; + unsigned long flags; + + bus = class_get_devdata(class_dev); + hcd = bus_to_hcd(bus); + ahcd = hcd_to_admhcd(hcd); + + /* display control and bulk lists together, for simplicity */ + spin_lock_irqsave(&ahcd->lock, flags); +#if 0 + temp = show_list (ahcd, buf, PAGE_SIZE, ahcd->ed_tails[ED_TAIL_CONTROL]); + temp += show_list (ahcd, buf + temp, PAGE_SIZE - temp, + ahcd->ed_tails[ED_TAIL_BULK]); +#else +#ifdef ED_TAIL_ARRAY + temp = show_list(ahcd, buf, PAGE_SIZE, ahcd->ed_head); +#else + temp = show_list(ahcd, buf, PAGE_SIZE, ahcd->ed_head); +#endif +#endif + spin_unlock_irqrestore(&ahcd->lock, flags); + + return temp; +} +static CLASS_DEVICE_ATTR(async, S_IRUGO, show_async, NULL); + + +#define DBG_SCHED_LIMIT 64 + +static ssize_t +show_periodic(struct class_device *class_dev, char *buf) +{ + struct usb_bus *bus; + struct usb_hcd *hcd; + struct admhcd *ahcd; + struct ed **seen, *ed; + unsigned long flags; + unsigned temp, size, seen_count; + char *next; + unsigned i; + + if (!(seen = kmalloc(DBG_SCHED_LIMIT * sizeof *seen, GFP_ATOMIC))) + return 0; + seen_count = 0; + + bus = class_get_devdata(class_dev); + hcd = bus_to_hcd(bus); + ahcd = hcd_to_admhcd(hcd); + next = buf; + size = PAGE_SIZE; + + temp = scnprintf(next, size, "size = %d\n", NUM_INTS); + size -= temp; + next += temp; + + /* dump a snapshot of the periodic schedule (and load) */ + spin_lock_irqsave(&ahcd->lock, flags); + for (i = 0; i < NUM_INTS; i++) { + if (!(ed = ahcd->periodic [i])) + continue; + + temp = scnprintf(next, size, "%2d [%3d]:", i, ahcd->load [i]); + size -= temp; + next += temp; + + do { + temp = scnprintf(next, size, " ed%d/%p", + ed->interval, ed); + size -= temp; + next += temp; + for (temp = 0; temp < seen_count; temp++) { + if (seen [temp] == ed) + break; + } + + /* show more info the first time around */ + if (temp == seen_count) { + u32 info = hc32_to_cpu (ahcd, ed->hwINFO); + struct list_head *entry; + unsigned qlen = 0; + + /* qlen measured here in TDs, not urbs */ + list_for_each (entry, &ed->td_list) + qlen++; + temp = scnprintf(next, size, + " (%cs dev%d ep%d%s qlen %u" + " max %d %08x%s%s)", + (info & ED_SPEED_FULL) ? 'f' : 'l', + ED_FA_GET(info), + ED_EN_GET(info), + (info & ED_ISO) ? "iso" : "int", + qlen, + ED_MPS_GET(info), + info, + (info & ED_SKIP) ? " K" : "", + (ed->hwHeadP & + cpu_to_hc32(ahcd, ED_H)) ? + " H" : ""); + size -= temp; + next += temp; + + if (seen_count < DBG_SCHED_LIMIT) + seen [seen_count++] = ed; + + ed = ed->ed_next; + + } else { + /* we've seen it and what's after */ + temp = 0; + ed = NULL; + } + + } while (ed); + + temp = scnprintf(next, size, "\n"); + size -= temp; + next += temp; + } + spin_unlock_irqrestore(&ahcd->lock, flags); + kfree (seen); + + return PAGE_SIZE - size; +} +static CLASS_DEVICE_ATTR(periodic, S_IRUGO, show_periodic, NULL); + + +#undef DBG_SCHED_LIMIT + +static ssize_t +show_registers(struct class_device *class_dev, char *buf) +{ + struct usb_bus *bus; + struct usb_hcd *hcd; + struct admhcd *ahcd; + struct admhcd_regs __iomem *regs; + unsigned long flags; + unsigned temp, size; + char *next; + u32 rdata; + + bus = class_get_devdata(class_dev); + hcd = bus_to_hcd(bus); + ahcd = hcd_to_admhcd(hcd); + regs = ahcd->regs; + next = buf; + size = PAGE_SIZE; + + spin_lock_irqsave(&ahcd->lock, flags); + + /* dump driver info, then registers in spec order */ + + admhc_dbg_sw(ahcd, &next, &size, + "bus %s, device %s\n" + "%s\n" + "%s version " DRIVER_VERSION "\n", + hcd->self.controller->bus->name, + hcd->self.controller->bus_id, + hcd->product_desc, + hcd_name); + + if (bus->controller->power.power_state.event) { + size -= scnprintf(next, size, + "SUSPENDED (no register access)\n"); + goto done; + } + + admhc_dump_status(ahcd, &next, &size); + + /* other registers mostly affect frame timings */ + rdata = admhc_readl(ahcd, ®s->fminterval); + temp = scnprintf(next, size, + "fmintvl 0x%08x %sFSLDP=0x%04x FI=0x%04x\n", + rdata, (rdata & ADMHC_SFI_FIT) ? "FIT " : "", + (rdata >> ADMHC_SFI_FSLDP_SHIFT) & ADMHC_SFI_FSLDP_MASK, + rdata & ADMHC_SFI_FI_MASK); + size -= temp; + next += temp; + + rdata = admhc_readl(ahcd, ®s->fmnumber); + temp = scnprintf(next, size, "fmnumber 0x%08x %sFR=0x%04x FN=%04x\n", + rdata, (rdata & ADMHC_SFN_FRT) ? "FRT " : "", + (rdata >> ADMHC_SFN_FR_SHIFT) & ADMHC_SFN_FR_MASK, + rdata & ADMHC_SFN_FN_MASK); + size -= temp; + next += temp; + + /* TODO: use predefined bitmask */ + rdata = admhc_readl(ahcd, ®s->lsthresh); + temp = scnprintf(next, size, "lsthresh 0x%04x\n", + rdata & 0x3fff); + size -= temp; + next += temp; + + temp = scnprintf(next, size, "hub poll timer: %s\n", + admhcd_to_hcd(ahcd)->poll_rh ? "ON" : "OFF"); + size -= temp; + next += temp; + + /* roothub */ + admhc_dump_roothub(ahcd, 1, &next, &size); + +done: + spin_unlock_irqrestore(&ahcd->lock, flags); + return PAGE_SIZE - size; +} +static CLASS_DEVICE_ATTR(registers, S_IRUGO, show_registers, NULL); + + +static inline void create_debug_files (struct admhcd *ahcd) +{ + struct class_device *cldev = admhcd_to_hcd(ahcd)->self.class_dev; + int retval; + + retval = class_device_create_file(cldev, &class_device_attr_async); + retval = class_device_create_file(cldev, &class_device_attr_periodic); + retval = class_device_create_file(cldev, &class_device_attr_registers); + admhc_dbg(ahcd, "created debug files\n"); +} + +static inline void remove_debug_files (struct admhcd *ahcd) +{ + struct class_device *cldev = admhcd_to_hcd(ahcd)->self.class_dev; + + class_device_remove_file(cldev, &class_device_attr_async); + class_device_remove_file(cldev, &class_device_attr_periodic); + class_device_remove_file(cldev, &class_device_attr_registers); +} + +#endif + +/*-------------------------------------------------------------------------*/ + diff --git a/target/linux/adm5120/files/drivers/usb/host/adm5120-drv.c b/target/linux/adm5120/files/drivers/usb/host/adm5120-drv.c new file mode 100644 index 000000000..208416e03 --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-drv.c @@ -0,0 +1,235 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * (C) Copyright 2002 Hewlett-Packard Company + * + * Bus Glue for AMD Alchemy Au1xxx + * + * Written by Christopher Hoover <ch@hpl.hp.com> + * Based on fragments of previous driver by Rusell King et al. + * + * Modified for LH7A404 from ahcd-sa1111.c + * by Durgesh Pattamatta <pattamattad@sharpsec.com> + * Modified for AMD Alchemy Au1xxx + * by Matt Porter <mporter@kernel.crashing.org> + * + * This file is licenced under the GPL. + */ + +#include <linux/platform_device.h> +#include <linux/signal.h> + +#include <asm/bootinfo.h> +#include <asm/mach-adm5120/adm5120_defs.h> + +#ifdef DEBUG +#define HCD_DBG(f, a...) printk(KERN_DEBUG "%s: " f, hcd_name, ## a) +#else +#define HCD_DBG(f, a...) do {} while (0) +#endif +#define HCD_ERR(f, a...) printk(KERN_ERR "%s: " f, hcd_name, ## a) +#define HCD_INFO(f, a...) printk(KERN_INFO "%s: " f, hcd_name, ## a) + +/*-------------------------------------------------------------------------*/ + +static int admhc_adm5120_probe(const struct hc_driver *driver, + struct platform_device *dev) +{ + int retval; + struct usb_hcd *hcd; + int irq; + struct resource *regs; + + /* sanity checks */ + regs = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!regs) { + HCD_DBG("no IOMEM resource found\n"); + return -ENODEV; + } + + irq = platform_get_irq(dev, 0); + if (irq < 0) { + HCD_DBG("no IRQ resource found\n"); + return -ENODEV; + } + + hcd = usb_create_hcd(driver, &dev->dev, "ADM5120"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = regs->start; + hcd->rsrc_len = regs->end - regs->start + 1; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + HCD_DBG("request_mem_region failed\n"); + retval = -EBUSY; + goto err_dev; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + HCD_DBG("ioremap failed\n"); + retval = -ENOMEM; + goto err_mem; + } + + admhc_hcd_init(hcd_to_admhcd(hcd)); + + retval = usb_add_hcd(hcd, irq, IRQF_DISABLED); + if (retval) + goto err_io; + + return 0; + +err_io: + iounmap(hcd->regs); +err_mem: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err_dev: + usb_put_hcd(hcd); + return retval; +} + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +static void admhc_adm5120_remove(struct usb_hcd *hcd, + struct platform_device *dev) +{ + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + +/*-------------------------------------------------------------------------*/ + +static int __devinit +admhc_adm5120_start(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd (hcd); + int ret; + + ret = admhc_init(ahcd); + if (ret < 0) { + HCD_ERR("unable to init %s\n", hcd->self.bus_name); + goto err; + } + + ret = admhc_run(ahcd); + if (ret < 0) { + HCD_ERR("unable to run %s\n", hcd->self.bus_name); + goto err_stop; + } + + return 0; + +err_stop: + admhc_stop(hcd); +err: + return ret; +} + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver adm5120_hc_driver = { + .description = hcd_name, + .product_desc = "ADM5120 built-in USB 1.1 Host Controller", + .hcd_priv_size = sizeof(struct admhcd), + + /* + * generic hardware linkage + */ + .irq = admhc_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .start = admhc_adm5120_start, + .stop = admhc_stop, + .shutdown = admhc_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = admhc_urb_enqueue, + .urb_dequeue = admhc_urb_dequeue, + .endpoint_disable = admhc_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = admhc_get_frame, + + /* + * root hub support + */ + .hub_status_data = admhc_hub_status_data, + .hub_control = admhc_hub_control, + .hub_irq_enable = admhc_rhsc_enable, +#ifdef CONFIG_PM + .bus_suspend = admhc_bus_suspend, + .bus_resume = admhc_bus_resume, +#endif + .start_port_reset = admhc_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + +static int usb_hcd_adm5120_probe(struct platform_device *pdev) +{ + int ret; + + if (mips_machgroup != MACH_GROUP_ADM5120) + return -ENODEV; + + ret = admhc_adm5120_probe(&adm5120_hc_driver, pdev); + + return ret; +} + +static int usb_hcd_adm5120_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + admhc_adm5120_remove(hcd, pdev); + + return 0; +} + +#if 0 +/* TODO */ +static int usb_hcd_adm5120_suspend(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + return 0; +} + +static int usb_hcd_adm5120_resume(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + return 0; +} +#endif + +static struct platform_driver usb_hcd_adm5120_driver = { + .probe = usb_hcd_adm5120_probe, + .remove = usb_hcd_adm5120_remove, + .shutdown = usb_hcd_platform_shutdown, +#if 0 + /* TODO */ + .suspend = usb_hcd_adm5120_suspend, + .resume = usb_hcd_adm5120_resume, +#endif + .driver = { + .name = "adm5120-hcd", + .owner = THIS_MODULE, + }, +}; + diff --git a/target/linux/adm5120/files/drivers/usb/host/adm5120-hcd.c b/target/linux/adm5120/files/drivers/usb/host/adm5120-hcd.c index 4e8877728..3365a1d0a 100644 --- a/target/linux/adm5120/files/drivers/usb/host/adm5120-hcd.c +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-hcd.c @@ -1,996 +1,914 @@ /* - * HCD driver for ADM5120 SoC + * OHCI HCD (Host Controller Driver) for USB. * - * Copyright (C) 2005 Jeroen Vreeken (pe1rxq@amsat.org) + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net> * - * Based on the ADMtek 2.4 driver - * (C) Copyright 2003 Junius Chen <juniusc@admtek.com.tw> - * Which again was based on the ohci and uhci drivers. + * [ Initialisation is based on Linus' ] + * [ uhci code and gregs ahcd fragments ] + * [ (C) Copyright 1999 Linus Torvalds ] + * [ (C) Copyright 1999 Gregory P. Smith] + * + * + * OHCI is the main "non-Intel/VIA" standard for USB 1.1 host controller + * interfaces (though some non-x86 Intel chips use it). It supports + * smarter hardware than UHCI. A download link for the spec available + * through the http://www.usb.org website. + * + * This file is licenced under the GPL. */ #include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/kernel.h> #include <linux/delay.h> -#include <linux/debugfs.h> -#include <linux/seq_file.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> #include <linux/errno.h> #include <linux/init.h> +#include <linux/timer.h> #include <linux/list.h> #include <linux/usb.h> -#include <linux/platform_device.h> +#include <linux/usb/otg.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/reboot.h> -#include <asm/bootinfo.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/system.h> +#include <asm/unaligned.h> #include <asm/byteorder.h> -#include <asm/mach-adm5120/adm5120_info.h> #include "../core/hcd.h" +#include "../core/hub.h" -MODULE_DESCRIPTION("ADM5120 USB Host Controller Driver"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jeroen Vreeken (pe1rxq@amsat.org)"); - -#define PFX "adm5120-hcd: " - -#define ADMHCD_REG_CONTROL 0x00 -#define ADMHCD_SW_RESET 0x00000008 /* Reset */ -#define ADMHCD_DMAA 0x00000004 /* DMA arbitration control */ -#define ADMHCD_SW_INTREQ 0x00000002 /* request software int */ -#define ADMHCD_HOST_EN 0x00000001 /* Host enable */ -#define ADMHCD_REG_INTSTATUS 0x04 -#define ADMHCD_INT_ACT 0x80000000 /* Interrupt active */ -#define ADMHCD_INT_FATAL 0x40000000 /* Fatal interrupt */ -#define ADMHCD_INT_SW 0x20000000 /* software interrupt */ -#define ADMHCD_INT_TD 0x00100000 /* TD completed */ -#define ADMHCD_FNO 0x00000800 /* Frame number overaflow */ -#define ADMHCD_SO 0x00000400 /* Scheduling overrun */ -#define ADMHCD_INSMI 0x00000200 /* Root hub status change */ -#define ADMHCD_BABI 0x00000100 /* Babble detected, host mode */ -#define ADMHCD_RESI 0x00000020 /* Resume detected */ -#define ADMHCD_SOFI 0x00000010 /* SOF transmitted/received, host mode */ -#define ADMHCD_REG_INTENABLE 0x08 -#define ADMHCD_INT_EN 0x80000000 /* Interrupt enable */ -#define ADMHCD_INTMASK 0x00000001 /* Interrupt mask */ -#define ADMHCD_REG_HOSTCONTROL 0x10 -#define ADMHCD_DMA_EN 0x00000004 /* USB host DMA enable */ -#define ADMHCD_STATE_MASK 0x00000003 -#define ADMHCD_STATE_RST 0x00000000 /* bus state reset */ -#define ADMHCD_STATE_RES 0x00000001 /* bus state resume */ -#define ADMHCD_STATE_OP 0x00000002 /* bus state operational */ -#define ADMHCD_STATE_SUS 0x00000003 /* bus state suspended */ -#define ADMHCD_REG_FMINTERVAL 0x18 -#define ADMHCD_REG_FMNUMBER 0x1c -#define ADMHCD_REG_LSTHRESH 0x70 -#define ADMHCD_REG_RHDESCR 0x74 -#define ADMHCD_CRWE 0x20000000 /* Clear wakeup enable */ -#define ADMHCD_DRWE 0x10000000 /* Device remote wakeup enable */ -#define ADMHCD_HW_OCIC 0x08000000 /* Over current indication change */ -#define ADMHCD_LPSC 0x04000000 /* Local power switch change */ -#define ADMHCD_OCI 0x02000000 /* Over current indication */ -#define ADMHCD_LPS 0x01000000 /* Local power switch/global power switch */ -#define ADMHCD_NOCP 0x00000800 /* No over current protect mode */ -#define ADMHCD_OPCM 0x00000400 /* Over current protect mode */ -#define ADMHCD_NPS 0x00000200 /* No Power Switch */ -#define ADMHCD_PSM 0x00000100 /* Power switch mode */ -#define ADMHCD_REG_PORTSTATUS0 0x78 -#define ADMHCD_CCS 0x00000001 /* current connect status */ -#define ADMHCD_PES 0x00000002 /* port enable status */ -#define ADMHCD_PSS 0x00000004 /* port suspend status */ -#define ADMHCD_POCI 0x00000008 /* port overcurrent indicator */ -#define ADMHCD_PRS 0x00000010 /* port reset status */ -#define ADMHCD_PPS 0x00000100 /* port power status */ -#define ADMHCD_LSDA 0x00000200 /* low speed device attached */ -#define ADMHCD_CSC 0x00010000 /* connect status change */ -#define ADMHCD_PESC 0x00020000 /* enable status change */ -#define ADMHCD_PSSC 0x00040000 /* suspend status change */ -#define ADMHCD_OCIC 0x00080000 /* overcurrent change*/ -#define ADMHCD_PRSC 0x00100000 /* reset status change */ -#define ADMHCD_REG_PORTSTATUS1 0x7c -#define ADMHCD_REG_HOSTHEAD 0x80 - -#define ADMHCD_NUMPORTS 1 -#define ADMHCD_DESC_ALIGN 16 - -struct admhcd_ed { - /* Don't change first four, they used for DMA */ - u32 control; - struct admhcd_td *tail; - struct admhcd_td *head; - struct admhcd_ed *next; - /* the rest is for the driver only: */ - struct admhcd_td *cur; - struct usb_host_endpoint *ep; - struct urb *urb; - struct admhcd_ed *real; -} __attribute__ ((packed)); - -#define ADMHCD_ED_EPSHIFT 7 /* Shift for endpoint number */ -#define ADMHCD_ED_INT 0x00000800 /* Is this an int endpoint */ -#define ADMHCD_ED_SPEED 0x00002000 /* Is it a high speed dev? */ -#define ADMHCD_ED_SKIP 0x00004000 /* Skip this ED */ -#define ADMHCD_ED_FORMAT 0x00008000 /* Is this an isoc endpoint */ -#define ADMHCD_ED_MAXSHIFT 16 /* Shift for max packet size */ - -struct admhcd_td { - /* Don't change first four, they are used for DMA */ - u32 control; - u32 buffer; - u32 buflen; - struct admhcd_td *next; - /* the rest is for the driver only: */ - struct urb *urb; - struct admhcd_td *real; -} __attribute__ ((packed)); - -#define ADMHCD_TD_OWN 0x80000000 -#define ADMHCD_TD_TOGGLE 0x00000000 -#define ADMHCD_TD_DATA0 0x01000000 -#define ADMHCD_TD_DATA1 0x01800000 -#define ADMHCD_TD_OUT 0x00200000 -#define ADMHCD_TD_IN 0x00400000 -#define ADMHCD_TD_SETUP 0x00000000 -#define ADMHCD_TD_ISO 0x00010000 -#define ADMHCD_TD_R 0x00040000 -#define ADMHCD_TD_INTEN 0x00010000 - -static int admhcd_td_err[16] = { - 0, /* No */ - -EREMOTEIO, /* CRC */ - -EREMOTEIO, /* bit stuff */ - -EREMOTEIO, /* data toggle */ - -EPIPE, /* stall */ - -ETIMEDOUT, /* timeout */ - -EPROTO, /* pid err */ - -EPROTO, /* unexpected pid */ - -EREMOTEIO, /* data overrun */ - -EREMOTEIO, /* data underrun */ - -ETIMEDOUT, /* 1010 */ - -ETIMEDOUT, /* 1011 */ - -EREMOTEIO, /* buffer overrun */ - -EREMOTEIO, /* buffer underrun */ - -ETIMEDOUT, /* 1110 */ - -ETIMEDOUT, /* 1111 */ -}; - -#define ADMHCD_TD_ERRMASK 0x38000000 -#define ADMHCD_TD_ERRSHIFT 27 - -#define TD(td) ((struct admhcd_td *)(((u32)(td)) & ~(ADMHCD_DESC_ALIGN-1))) -#define ED(ed) ((struct admhcd_ed *)(((u32)(ed)) & ~(ADMHCD_DESC_ALIGN-1))) - -struct admhcd { - spinlock_t lock; - - /* Root hub registers */ - u32 rhdesca; - u32 rhdescb; - u32 rhstatus; - u32 rhport[2]; - - /* async schedule: control, bulk */ - struct list_head async; - u32 base; - u32 dma_en; - unsigned long flags; -}; +#define DRIVER_VERSION "v0.01" +#define DRIVER_AUTHOR "Gabor Juhos <juhosg at openwrt.org>" +#define DRIVER_DESC "ADMtek USB 1.1 Host Controller Driver" -static inline struct admhcd *hcd_to_admhcd(struct usb_hcd *hcd) -{ - return (struct admhcd *)(hcd->hcd_priv); -} +/*-------------------------------------------------------------------------*/ -static inline struct usb_hcd *admhcd_to_hcd(struct admhcd *admhcd) -{ - return container_of((void *)admhcd, struct usb_hcd, hcd_priv); -} +#define ADMHC_VERBOSE_DEBUG /* not always helpful */ +#undef LATE_ED_SCHEDULE -static char hcd_name[] = "adm5120-hcd"; +/* For initializing controller (mask in an HCFS mode too) */ +#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR -static u32 admhcd_reg_get(struct admhcd *ahcd, int reg) -{ - return *(volatile u32 *)KSEG1ADDR(ahcd->base+reg); -} +#define ADMHC_INTR_INIT \ + ( ADMHC_INTR_MIE | ADMHC_INTR_INSM | ADMHC_INTR_FATI \ + | ADMHC_INTR_RESI | ADMHC_INTR_TDC | ADMHC_INTR_BABI ) -static void admhcd_reg_set(struct admhcd *ahcd, int reg, u32 val) -{ - *(volatile u32 *)KSEG1ADDR(ahcd->base+reg) = val; -} +/*-------------------------------------------------------------------------*/ -static void admhcd_lock(struct admhcd *ahcd) -{ - spin_lock_irqsave(&ahcd->lock, ahcd->flags); - ahcd->dma_en = admhcd_reg_get(ahcd, ADMHCD_REG_HOSTCONTROL) & - ADMHCD_DMA_EN; - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, ADMHCD_STATE_OP); -} +static const char hcd_name [] = "admhc-hcd"; -static void admhcd_unlock(struct admhcd *ahcd) -{ - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, - ADMHCD_STATE_OP | ahcd->dma_en); - spin_unlock_irqrestore(&ahcd->lock, ahcd->flags); -} +#define STATECHANGE_DELAY msecs_to_jiffies(300) -static struct admhcd_td *admhcd_td_alloc(struct admhcd_ed *ed, struct urb *urb) -{ - struct admhcd_td *tdn, *td; - - tdn = kzalloc(sizeof(*tdn)+ADMHCD_DESC_ALIGN, GFP_ATOMIC); - if (!tdn) - return NULL; - - tdn->real = tdn; - tdn = TD(KSEG1ADDR(tdn)); - if (ed->cur == NULL) { - ed->cur = tdn; - ed->head = tdn; - ed->tail = tdn; - td = tdn; - } else { - /* Supply back the old tail and link in new td as tail */ - td = TD(ed->tail); - TD(ed->tail)->next = tdn; - ed->tail = tdn; - } - td->urb = urb; - - return td; -} +#include "adm5120.h" -static void admhcd_td_free(struct admhcd_ed *ed, struct urb *urb) -{ - struct admhcd_td *td, **tdp; - - if (urb == NULL) - ed->control |= ADMHCD_ED_SKIP; - tdp = &ed->cur; - td = ed->cur; - do { - if (td->urb == urb) - break; - tdp = &td->next; - td = TD(td->next); - } while (td); - while (td && td->urb == urb) { - *tdp = TD(td->next); - kfree(td->real); - td = *tdp; - } -} - -/* Find an endpoint's descriptor, if needed allocate a new one and link it - in the DMA chain - */ -static struct admhcd_ed *admhcd_get_ed(struct admhcd *ahcd, - struct usb_host_endpoint *ep, struct urb *urb) -{ - struct admhcd_ed *hosthead; - struct admhcd_ed *found = NULL, *ed = NULL; - unsigned int pipe = urb->pipe; - - admhcd_lock(ahcd); - hosthead = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD); - if (hosthead) { - for (ed = hosthead;; ed = ED(ed->next)) { - if (ed->ep == ep) { - found = ed; - break; - } - if (ED(ed->next) == hosthead) - break; - } - } - if (!found) { - found = kzalloc(sizeof(*found)+ADMHCD_DESC_ALIGN, GFP_ATOMIC); - if (!found) - goto out; - found->real = found; - found->ep = ep; - found = ED(KSEG1ADDR(found)); - found->control = usb_pipedevice(pipe) | - (usb_pipeendpoint(pipe) << ADMHCD_ED_EPSHIFT) | - (usb_pipeint(pipe) ? ADMHCD_ED_INT : 0) | - (urb->dev->speed == USB_SPEED_FULL ? ADMHCD_ED_SPEED : 0) | - (usb_pipeisoc(pipe) ? ADMHCD_ED_FORMAT : 0) | - (usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)) << ADMHCD_ED_MAXSHIFT); - /* Alloc first dummy td */ - admhcd_td_alloc(found, NULL); - if (hosthead) { - found->next = hosthead; - ed->next = found; - } else { - found->next = found; - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, (u32)found); - } - } -out: - admhcd_unlock(ahcd); - return found; -} +static void admhc_dump(struct admhcd *ahcd, int verbose); +static int admhc_init(struct admhcd *ahcd); +static void admhc_stop(struct usb_hcd *hcd); -static struct admhcd_td *admhcd_td_fill(u32 control, struct admhcd_td *td, - dma_addr_t data, int len) -{ - td->buffer = data; - td->buflen = len; - td->control = control; - return TD(td->next); -} +#include "adm5120-hub.c" +#include "adm5120-dbg.c" +#include "adm5120-mem.c" +#include "adm5120-q.c" -static void admhcd_ed_start(struct admhcd *ahcd, struct admhcd_ed *ed) -{ - struct admhcd_td *td = ed->cur; +/*-------------------------------------------------------------------------*/ - if (ed->urb) - return; - if (td->urb) { - ed->urb = td->urb; - while (1) { - td->control |= ADMHCD_TD_OWN; - if (TD(td->next)->urb != td->urb) { - td->buflen |= ADMHCD_TD_INTEN; - break; - } - td = TD(td->next); - } - } - ed->head = TD(ed->head); - ahcd->dma_en |= ADMHCD_DMA_EN; -} - -static irqreturn_t admhcd_irq(struct usb_hcd *hcd) +/* + * queue up an urb for anything except the root hub + */ +static int admhc_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t mem_flags) { - struct admhcd *ahcd = hcd_to_admhcd(hcd); - u32 intstatus; - - intstatus = admhcd_reg_get(ahcd, ADMHCD_REG_INTSTATUS); - if (intstatus & ADMHCD_INT_FATAL) { - admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_FATAL); - /* FIXME: handle fatal interrupts */ - } - - if (intstatus & ADMHCD_INT_SW) { - admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_SW); - /* FIXME: handle software interrupts */ - } - - if (intstatus & ADMHCD_INT_TD) { - struct admhcd_ed *ed, *head; - - admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_TD); - - head = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD); - ed = head; - if (ed) do { - /* Is it a finished TD? */ - if (ed->urb && !(ed->cur->control & ADMHCD_TD_OWN)) { - struct admhcd_td *td; - int error; - - td = ed->cur; - error = (td->control & ADMHCD_TD_ERRMASK) >> - ADMHCD_TD_ERRSHIFT; - ed->urb->status = admhcd_td_err[error]; - admhcd_td_free(ed, ed->urb); - // Calculate real length!!! - ed->urb->actual_length = ed->urb->transfer_buffer_length; - ed->urb->hcpriv = NULL; - usb_hcd_giveback_urb(hcd, ed->urb); - ed->urb = NULL; - } - admhcd_ed_start(ahcd, ed); - ed = ED(ed->next); - } while (ed != head); - } + struct admhcd *ahcd = hcd_to_admhcd(hcd); + struct ed *ed; + struct urb_priv *urb_priv; + unsigned int pipe = urb->pipe; + int i, td_cnt = 0; + unsigned long flags; + int retval = 0; - return IRQ_HANDLED; -} +#ifdef ADMHC_VERBOSE_DEBUG + urb_print(urb, "ENQ", usb_pipein(pipe)); +#endif -static int admhcd_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep, - struct urb *urb, gfp_t mem_flags) -{ - struct admhcd *ahcd = hcd_to_admhcd(hcd); - struct admhcd_ed *ed; - struct admhcd_td *td; - int size = 0, i, zero = 0, ret = 0; - unsigned int pipe = urb->pipe, toggle = 0; - dma_addr_t data = (dma_addr_t)urb->transfer_buffer; - int data_len = urb->transfer_buffer_length; - - ed = admhcd_get_ed(ahcd, ep, urb); + /* every endpoint has an ed, locate and maybe (re)initialize it */ + ed = ed_get(ahcd, ep, urb->dev, pipe, urb->interval); if (!ed) return -ENOMEM; - switch(usb_pipetype(pipe)) { + /* for the private part of the URB we need the number of TDs */ + switch (ed->type) { case PIPE_CONTROL: - size = 2; - case PIPE_INTERRUPT: + if (urb->transfer_buffer_length > TD_DATALEN_MAX) + /* td_submit_urb() doesn't yet handle these */ + return -EMSGSIZE; + + /* 1 TD for setup, 1 for ACK, plus ... */ + td_cnt = 2; + /* FALLTHROUGH */ case PIPE_BULK: - default: - size += urb->transfer_buffer_length / 4096; - if (urb->transfer_buffer_length % 4096) - size++; - if (size == 0) - size++; - else if (urb->transfer_flags & URB_ZERO_PACKET && - !(urb->transfer_buffer_length % - usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)))) { - size++; - zero = 1; - } + /* one TD for every 4096 Bytes (can be upto 8K) */ + td_cnt += urb->transfer_buffer_length / TD_DATALEN_MAX; + /* ... and for any remaining bytes ... */ + if ((urb->transfer_buffer_length % TD_DATALEN_MAX) != 0) + td_cnt++; + /* ... and maybe a zero length packet to wrap it up */ + if (td_cnt == 0) + td_cnt++; + else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 + && (urb->transfer_buffer_length + % usb_maxpacket(urb->dev, pipe, + usb_pipeout (pipe))) == 0) + td_cnt++; + break; + case PIPE_INTERRUPT: + /* + * for Interrupt IN/OUT transactions, each ED contains + * only 1 TD. + * TODO: check transfer_buffer_length? + */ + td_cnt = 1; break; case PIPE_ISOCHRONOUS: - size = urb->number_of_packets; + /* number of packets from URB */ + td_cnt = urb->number_of_packets; break; + default: + /* paranoia */ + admhc_err(ahcd, "bad EP type %d", ed->type); + return -EINVAL; } - admhcd_lock(ahcd); - /* Remember the first td */ - td = admhcd_td_alloc(ed, urb); - if (!td) { - ret = -ENOMEM; - goto out; + urb_priv = urb_priv_alloc(ahcd, td_cnt, mem_flags); + if (!urb_priv) + return -ENOMEM; + + urb_priv->ed = ed; + + spin_lock_irqsave(&ahcd->lock, flags); + /* don't submit to a dead HC */ + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + retval = -ENODEV; + goto fail; } - /* Allocate additionall tds first */ - for (i = 1; i < size; i++) { - if (admhcd_td_alloc(ed, urb) == NULL) { - admhcd_td_free(ed, urb); - ret = -ENOMEM; - goto out; - } + if (!HC_IS_RUNNING(hcd->state)) { + retval = -ENODEV; + goto fail; } - if (usb_gettoggle(urb->dev, usb_pipeendpoint(pipe), usb_pipeout(pipe))) - toggle = ADMHCD_TD_TOGGLE; - else { - toggle = ADMHCD_TD_DATA0; - usb_settoggle(urb->dev, usb_pipeendpoint(pipe), - usb_pipeout(pipe), 1); + /* in case of unlink-during-submit */ + spin_lock(&urb->lock); + if (urb->status != -EINPROGRESS) { + spin_unlock(&urb->lock); + urb->hcpriv = urb_priv; + finish_urb(ahcd, urb); + retval = 0; + goto fail; } - switch(usb_pipetype(pipe)) { - case PIPE_CONTROL: - td = admhcd_td_fill(ADMHCD_TD_SETUP | ADMHCD_TD_DATA0, - td, (dma_addr_t)urb->setup_packet, 8); - while (data_len > 0) { - td = admhcd_td_fill(ADMHCD_TD_DATA1 - | ADMHCD_TD_R | - (usb_pipeout(pipe) ? - ADMHCD_TD_OUT : ADMHCD_TD_IN), td, - data, data_len % 4097); - data_len -= 4096; - } - admhcd_td_fill(ADMHCD_TD_DATA1 | (usb_pipeout(pipe) ? - ADMHCD_TD_IN : ADMHCD_TD_OUT), td, - data, 0); - break; - case PIPE_INTERRUPT: - case PIPE_BULK: - //info ok for interrupt? - i = 0; - while(data_len > 4096) { - td = admhcd_td_fill((usb_pipeout(pipe) ? - ADMHCD_TD_OUT : - ADMHCD_TD_IN | ADMHCD_TD_R) | - (i ? ADMHCD_TD_TOGGLE : toggle), td, - data, 4096); - data += 4096; - data_len -= 4096; - i++; - } - td = admhcd_td_fill((usb_pipeout(pipe) ? - ADMHCD_TD_OUT : ADMHCD_TD_IN) | - (i ? ADMHCD_TD_TOGGLE : toggle), td, data, data_len); - i++; - if (zero) - admhcd_td_fill((usb_pipeout(pipe) ? - ADMHCD_TD_OUT : ADMHCD_TD_IN) | - (i ? ADMHCD_TD_TOGGLE : toggle), td, 0, 0); - break; - case PIPE_ISOCHRONOUS: - for (i = 0; i < urb->number_of_packets; i++) { - td = admhcd_td_fill(ADMHCD_TD_ISO | - ((urb->start_frame + i) & 0xffff), td, - data + urb->iso_frame_desc[i].offset, - urb->iso_frame_desc[i].length); + /* schedule the ed if needed */ + if (ed->state == ED_IDLE) { +#ifndef LATE_ED_SCHEDULE + retval = ed_schedule(ahcd, ed); + if (retval < 0) + goto fail0; +#endif + if (ed->type == PIPE_ISOCHRONOUS) { + u16 frame = admhc_frame_no(ahcd); + + /* delay a few frames before the first TD */ + frame += max_t (u16, 8, ed->interval); + frame &= ~(ed->interval - 1); + frame |= ed->branch; + urb->start_frame = frame; + + /* yes, only URB_ISO_ASAP is supported, and + * urb->start_frame is never used as input. + */ } - break; - } - urb->hcpriv = ed; - admhcd_ed_start(ahcd, ed); -out: - admhcd_unlock(ahcd); - return ret; + } else if (ed->type == PIPE_ISOCHRONOUS) + urb->start_frame = ed->last_iso + ed->interval; + + /* fill the TDs and link them to the ed; and + * enable that part of the schedule, if needed + * and update count of queued periodic urbs + */ + urb->hcpriv = urb_priv; + td_submit_urb(ahcd, urb); + +#ifdef LATE_ED_SCHEDULE + if (ed->state == ED_IDLE) + retval = ed_schedule(ahcd, ed); +#endif + + admhc_dump_ed(ahcd, "admhc_urb_enqueue", urb_priv->ed, 1); + +fail0: + spin_unlock(&urb->lock); +fail: + if (retval) + urb_priv_free(ahcd, urb_priv); + + spin_unlock_irqrestore(&ahcd->lock, flags); + return retval; } -static int admhcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) +/* + * decouple the URB from the HC queues (TDs, urb_priv); it's + * already marked using urb->status. reporting is always done + * asynchronously, and we might be dealing with an urb that's + * partially transferred, or an ED with other urbs being unlinked. + */ +static int admhc_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) { - struct admhcd *ahcd = hcd_to_admhcd(hcd); - struct admhcd_ed *ed; + struct admhcd *ahcd = hcd_to_admhcd(hcd); + unsigned long flags; - admhcd_lock(ahcd); +#ifdef ADMHC_VERBOSE_DEBUG + urb_print(urb, "DEQ", 1); +#endif - ed = urb->hcpriv; - if (ed && ed->urb != urb) - admhcd_td_free(ed, urb); + spin_lock_irqsave(&ahcd->lock, flags); + if (HC_IS_RUNNING(hcd->state)) { + struct urb_priv *urb_priv; + + /* Unless an IRQ completed the unlink while it was being + * handed to us, flag it for unlink and giveback, and force + * some upcoming INTR_SF to call finish_unlinks() + */ + urb_priv = urb->hcpriv; + if (urb_priv) { + if (urb_priv->ed->state == ED_OPER) + start_ed_unlink(ahcd, urb_priv->ed); + } + } else { + /* + * with HC dead, we won't respect hc queue pointers + * any more ... just clean up every urb's memory. + */ + if (urb->hcpriv) + finish_urb(ahcd, urb); + } + spin_unlock_irqrestore(&ahcd->lock, flags); - admhcd_unlock(ahcd); return 0; } -static void admhcd_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +/*-------------------------------------------------------------------------*/ + +/* frees config/altsetting state for endpoints, + * including ED memory, dummy TD, and bulk/intr data toggle + */ + +static void +admhc_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) { - struct admhcd *ahcd = hcd_to_admhcd(hcd); - struct admhcd_ed *ed, *edt, *head; + struct admhcd *ahcd = hcd_to_admhcd(hcd); + unsigned long flags; + struct ed *ed = ep->hcpriv; + unsigned limit = 1000; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + + if (!ed) + return; + +#ifdef ADMHC_VERBOSE_DEBUG + spin_lock_irqsave(&ahcd->lock, flags); + admhc_dump_ed(ahcd, "ep_disable", ed, 1); + spin_unlock_irqrestore(&ahcd->lock, flags); +#endif - admhcd_lock(ahcd); +rescan: + spin_lock_irqsave(&ahcd->lock, flags); + + if (!HC_IS_RUNNING(hcd->state)) { +sanitize: + ed->state = ED_IDLE; + finish_unlinks(ahcd, 0); + } - head = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD); - if (!head) - goto out; - for (ed = head; ED(ed->next) != head; ed = ED(ed->next)) - if (ed->ep == ep) + switch (ed->state) { + case ED_UNLINK: /* wait for hw to finish? */ + /* major IRQ delivery trouble loses INTR_SOFI too... */ + if (limit-- == 0) { + admhc_warn(ahcd, "IRQ INTR_SOFI lossage\n"); + goto sanitize; + } + spin_unlock_irqrestore(&ahcd->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case ED_IDLE: /* fully unlinked */ + if (list_empty(&ed->td_list)) { + td_free (ahcd, ed->dummy); + ed_free (ahcd, ed); break; - if (ed->ep != ep) - goto out; - while (ed->cur) - admhcd_td_free(ed, ed->cur->urb); - if (head == ed) { - if (ED(ed->next) == ed) { - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, 0); - ahcd->dma_en = 0; - goto out_free; } - head = ED(ed->next); - for (edt = head; ED(edt->next) != head; edt = ED(edt->next)); - edt->next = ED(ed->next); - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, (u32)ed->next); - goto out_free; + /* else FALL THROUGH */ + default: + /* caller was supposed to have unlinked any requests; + * that's not our job. can't recover; must leak ed. + */ + admhc_err(ahcd, "leak ed %p (#%02x) state %d%s\n", + ed, ep->desc.bEndpointAddress, ed->state, + list_empty(&ed->td_list) ? "" : " (has tds)"); + td_free(ahcd, ed->dummy); + break; } - for (edt = head; edt->next != ed; edt = edt->next); - edt->next = ed->next; - -out_free: - kfree(ed->real); -out: - admhcd_unlock(ahcd); + ep->hcpriv = NULL; + spin_unlock_irqrestore(&ahcd->lock, flags); + return; } -static int admhcd_get_frame_number(struct usb_hcd *hcd) +static int admhc_get_frame(struct usb_hcd *hcd) { struct admhcd *ahcd = hcd_to_admhcd(hcd); - return admhcd_reg_get(ahcd, ADMHCD_REG_FMNUMBER) & 0x0000ffff; + return admhc_frame_no(ahcd); } -static int admhcd_hub_status_data(struct usb_hcd *hcd, char *buf) +static void admhc_usb_reset(struct admhcd *ahcd) { - struct admhcd *ahcd = hcd_to_admhcd(hcd); - int port; - - *buf = 0; - for (port = 0; port < ADMHCD_NUMPORTS; port++) { - if (admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4) & - (ADMHCD_CSC | ADMHCD_PESC | ADMHCD_PSSC | ADMHCD_OCIC | - ADMHCD_PRSC)) - *buf |= (1 << (port + 1)); - } - return !!*buf; +#if 0 + ahcd->hc_control = admhc_readl(ahcd, &ahcd->regs->control); + ahcd->hc_control &= OHCI_CTRL_RWC; + admhc_writel(ahcd, ahcd->hc_control, &ahcd->regs->control); +#else + /* FIXME */ + ahcd->host_control = ADMHC_BUSS_RESET; + admhc_writel(ahcd, ahcd->host_control ,&ahcd->regs->host_control); +#endif } -static __u8 root_hub_hub_des[] = { - 0x09, /* __u8 bLength; */ - 0x29, /* __u8 bDescriptorType; Hub-descriptor */ - 0x02, /* __u8 bNbrPorts; */ - 0x0a, 0x00, /* __u16 wHubCharacteristics; */ - 0x01, /* __u8 bPwrOn2pwrGood; 2ms */ - 0x00, /* __u8 bHubContrCurrent; 0mA */ - 0x00, /* __u8 DeviceRemovable; */ - 0xff, /* __u8 PortPwrCtrlMask; */ -}; - -static int admhcd_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, - u16 wIndex, char *buf, u16 wLength) +/* admhc_shutdown forcibly disables IRQs and DMA, helping kexec and + * other cases where the next software may expect clean state from the + * "firmware". this is bus-neutral, unlike shutdown() methods. + */ +static void +admhc_shutdown(struct usb_hcd *hcd) { - struct admhcd *ahcd = hcd_to_admhcd(hcd); - int retval = 0, len; - unsigned int port = wIndex -1; + struct admhcd *ahcd; - switch (typeReq) { + ahcd = hcd_to_admhcd(hcd); + admhc_intr_disable(ahcd, ADMHC_INTR_MIE); + admhc_dma_disable(ahcd); + admhc_usb_reset(ahcd); + /* flush the writes */ + admhc_writel_flush(ahcd); +} - case GetHubStatus: - *(__le32 *)buf = cpu_to_le32(0); - break; - case GetPortStatus: - if (port >= ADMHCD_NUMPORTS) - goto err; - *(__le32 *)buf = cpu_to_le32( - admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4)); - break; - case SetHubFeature: /* We don't implement these */ - case ClearHubFeature: - switch (wValue) { - case C_HUB_OVER_CURRENT: - case C_HUB_LOCAL_POWER: - break; - default: - goto err; - } - case SetPortFeature: - if (port >= ADMHCD_NUMPORTS) - goto err; - - switch (wValue) { - case USB_PORT_FEAT_SUSPEND: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_PSS); - break; - case USB_PORT_FEAT_RESET: - if (admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4) - & ADMHCD_CCS) { - admhcd_reg_set(ahcd, - ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_PRS | ADMHCD_CSC); - mdelay(50); - admhcd_reg_set(ahcd, - ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_PES | ADMHCD_CSC); - } - break; - case USB_PORT_FEAT_POWER: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_PPS); - break; - default: - goto err; - } - break; - case ClearPortFeature: - if (port >= ADMHCD_NUMPORTS) - goto err; - - switch (wValue) { - case USB_PORT_FEAT_ENABLE: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_CCS); - break; - case USB_PORT_FEAT_C_ENABLE: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_PESC); - break; - case USB_PORT_FEAT_SUSPEND: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_POCI); - break; - case USB_PORT_FEAT_C_SUSPEND: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_PSSC); - case USB_PORT_FEAT_POWER: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_LSDA); - break; - case USB_PORT_FEAT_C_CONNECTION: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_CSC); - break; - case USB_PORT_FEAT_C_OVER_CURRENT: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_OCIC); - break; - case USB_PORT_FEAT_C_RESET: - admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, - ADMHCD_PRSC); - break; - default: - goto err; - } - break; - case GetHubDescriptor: - len = min_t(unsigned int, sizeof(root_hub_hub_des), wLength); - memcpy(buf, root_hub_hub_des, len); - break; - default: -err: - retval = -EPIPE; +/*-------------------------------------------------------------------------* + * HC functions + *-------------------------------------------------------------------------*/ + +static void admhc_eds_cleanup(struct admhcd *ahcd) +{ + if (ahcd->ed_tails[PIPE_INTERRUPT]) { + ed_free(ahcd, ahcd->ed_tails[PIPE_INTERRUPT]); + ahcd->ed_tails[PIPE_INTERRUPT] = NULL; } - return retval; -} + if (ahcd->ed_tails[PIPE_ISOCHRONOUS]) { + ed_free(ahcd, ahcd->ed_tails[PIPE_ISOCHRONOUS]); + ahcd->ed_tails[PIPE_ISOCHRONOUS] = NULL; + } -static int admhcd_start(struct usb_hcd *hcd) -{ - struct admhcd *ahcd = hcd_to_admhcd(hcd); - unsigned long flags; + if (ahcd->ed_tails[PIPE_CONTROL]) { + ed_free(ahcd, ahcd->ed_tails[PIPE_CONTROL]); + ahcd->ed_tails[PIPE_CONTROL] = NULL; + } - spin_lock_irqsave(&ahcd->lock, flags); + if (ahcd->ed_tails[PIPE_BULK]) { + ed_free(ahcd, ahcd->ed_tails[PIPE_BULK]); + ahcd->ed_tails[PIPE_BULK] = NULL; + } - /* Initialise the HCD registers */ - admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0); - mdelay(10); + ahcd->ed_head = NULL; +} - admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_SW_RESET); +#define ED_DUMMY_INFO (ED_SPEED_FULL | ED_SKIP) - while (admhcd_reg_get(ahcd, ADMHCD_REG_CONTROL) & ADMHCD_SW_RESET) { - printk(KERN_WARNING PFX "waiting for reset to complete\n"); - mdelay(1); - } +static int admhc_eds_init(struct admhcd *ahcd) +{ + struct ed *ed; - //hcd->uses_new_polling = 1; + ed = ed_create(ahcd, PIPE_INTERRUPT, ED_DUMMY_INFO); + if (!ed) + goto err; - /* Enable USB host mode */ - admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_HOST_EN); + ahcd->ed_tails[PIPE_INTERRUPT] = ed; - /* Set host specific settings */ - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, 0x00000000); - admhcd_reg_set(ahcd, ADMHCD_REG_FMINTERVAL, 0x20002edf); - admhcd_reg_set(ahcd, ADMHCD_REG_LSTHRESH, 0x628); + ed = ed_create(ahcd, PIPE_ISOCHRONOUS, ED_DUMMY_INFO); + if (!ed) + goto err; - /* Set interrupts */ - admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, ADMHCD_INT_ACT | - ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD); - admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_ACT | - ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD); + ahcd->ed_tails[PIPE_ISOCHRONOUS] = ed; + ed->ed_prev = ahcd->ed_tails[PIPE_INTERRUPT]; + ahcd->ed_tails[PIPE_INTERRUPT]->ed_next = ed; + ahcd->ed_tails[PIPE_INTERRUPT]->hwNextED = cpu_to_hc32(ahcd, ed->dma); - /* Power on all ports */ - admhcd_reg_set(ahcd, ADMHCD_REG_RHDESCR, ADMHCD_NPS | ADMHCD_LPSC); + ed = ed_create(ahcd, PIPE_CONTROL, ED_DUMMY_INFO); + if (!ed) + goto err; - /* HCD is now operationnal */ - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, ADMHCD_STATE_OP); + ahcd->ed_tails[PIPE_CONTROL] = ed; + ed->ed_prev = ahcd->ed_tails[PIPE_ISOCHRONOUS]; + ahcd->ed_tails[PIPE_ISOCHRONOUS]->ed_next = ed; + ahcd->ed_tails[PIPE_ISOCHRONOUS]->hwNextED = cpu_to_hc32(ahcd, ed->dma); - hcd->state = HC_STATE_RUNNING; + ed = ed_create(ahcd, PIPE_BULK, ED_DUMMY_INFO); + if (!ed) + goto err; - spin_unlock_irqrestore(&ahcd->lock, flags); + ahcd->ed_tails[PIPE_BULK] = ed; + ed->ed_prev = ahcd->ed_tails[PIPE_CONTROL]; + ahcd->ed_tails[PIPE_CONTROL]->ed_next = ed; + ahcd->ed_tails[PIPE_CONTROL]->hwNextED = cpu_to_hc32(ahcd, ed->dma); + + ahcd->ed_head = ahcd->ed_tails[PIPE_INTERRUPT]; + +#ifdef ADMHC_VERBOSE_DEBUG + admhc_dump_ed(ahcd, "ed intr", ahcd->ed_tails[PIPE_INTERRUPT], 1); + admhc_dump_ed(ahcd, "ed isoc", ahcd->ed_tails[PIPE_ISOCHRONOUS], 1); + admhc_dump_ed(ahcd, "ed ctrl", ahcd->ed_tails[PIPE_CONTROL], 1); + admhc_dump_ed(ahcd, "ed bulk", ahcd->ed_tails[PIPE_BULK], 1); +#endif return 0; + +err: + admhc_eds_cleanup(ahcd); + return -ENOMEM; } -static int admhcd_sw_reset(struct admhcd *ahcd) +/* init memory, and kick BIOS/SMM off */ + +static int admhc_init(struct admhcd *ahcd) { - int retries = 15; - unsigned long flags; - int ret = 0; + struct usb_hcd *hcd = admhcd_to_hcd(ahcd); + int ret; - spin_lock_irqsave(&ahcd->lock, flags); + admhc_disable(ahcd); + ahcd->regs = hcd->regs; - admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0); - mdelay(10); + /* Disable HC interrupts */ + admhc_intr_disable(ahcd, ADMHC_INTR_MIE); - admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_SW_RESET); + /* Read the number of ports unless overridden */ + if (ahcd->num_ports == 0) + ahcd->num_ports = admhc_get_rhdesc(ahcd) & ADMHC_RH_NUMP; - while (--retries) { - mdelay(1); - if (!(admhcd_reg_get(ahcd, ADMHCD_REG_CONTROL) & ADMHCD_SW_RESET)) - break; - } - if (!retries) { - printk(KERN_WARNING "%s: software reset timeout\n", hcd_name); - ret = -ETIME; - } - spin_unlock_irqrestore(&ahcd->lock, flags); + ret = admhc_mem_init(ahcd); + if (ret) + goto err; + + /* init dummy endpoints */ + ret = admhc_eds_init(ahcd); + if (ret) + goto err; + + create_debug_files(ahcd); + + return 0; + +err: + admhc_stop(hcd); return ret; } -static int admhcd_reset(struct usb_hcd *hcd) +/*-------------------------------------------------------------------------*/ + +/* Start an OHCI controller, set the BUS operational + * resets USB and controller + * enable interrupts + */ +static int admhc_run(struct admhcd *ahcd) { - struct admhcd *ahcd = hcd_to_admhcd(hcd); - u32 state = 0; - int ret, timeout = 15; /* ms */ - unsigned long t; + u32 temp; + int first = ahcd->fminterval == 0; + struct usb_hcd *hcd = admhcd_to_hcd(ahcd); + + admhc_disable(ahcd); + + /* boot firmware should have set this up (5.1.1.3.1) */ + if (first) { + temp = admhc_readl(ahcd, &ahcd->regs->fminterval); + ahcd->fminterval = temp & ADMHC_SFI_FI_MASK; + if (ahcd->fminterval != FI) + admhc_dbg(ahcd, "fminterval delta %d\n", + ahcd->fminterval - FI); + ahcd->fminterval |= + (FSLDP (ahcd->fminterval) << ADMHC_SFI_FSLDP_SHIFT); + /* also: power/overcurrent flags in rhdesc */ + } - ret = admhcd_sw_reset(ahcd); - if (ret) - return ret; - - t = jiffies + msecs_to_jiffies(timeout); - do { - spin_lock_irq(&ahcd->lock); - state = admhcd_reg_get(ahcd, ADMHCD_REG_HOSTCONTROL); - spin_unlock_irq(&ahcd->lock); - state &= ADMHCD_STATE_MASK; - if (state == ADMHCD_STATE_RST) - break; - msleep(4); - } while (time_before_eq(jiffies, t)); +#if 0 /* TODO: not applicable */ + /* Reset USB nearly "by the book". RemoteWakeupConnected was + * saved if boot firmware (BIOS/SMM/...) told us it's connected, + * or if bus glue did the same (e.g. for PCI add-in cards with + * PCI PM support). + */ + if ((ahcd->hc_control & OHCI_CTRL_RWC) != 0 + && !device_may_wakeup(hcd->self.controller)) + device_init_wakeup(hcd->self.controller, 1); +#endif - if (state != ADMHCD_STATE_RST) { - printk(KERN_WARNING "%s: device not ready after %dms\n", - hcd_name, timeout); - ret = -ENODEV; + switch (ahcd->host_control & ADMHC_HC_BUSS) { + case ADMHC_BUSS_OPER: + temp = 0; + break; + case ADMHC_BUSS_SUSPEND: + /* FALLTHROUGH ? */ + case ADMHC_BUSS_RESUME: + ahcd->host_control = ADMHC_BUSS_RESUME; + temp = 10 /* msec wait */; + break; + /* case ADMHC_BUSS_RESET: */ + default: + ahcd->host_control = ADMHC_BUSS_RESET; + temp = 50 /* msec wait */; + break; + } + admhc_writel(ahcd, ahcd->host_control, &ahcd->regs->host_control); + + /* flush the writes */ + admhc_writel_flush(ahcd); + + msleep(temp); + temp = admhc_get_rhdesc(ahcd); + if (!(temp & ADMHC_RH_NPS)) { + /* power down each port */ + for (temp = 0; temp < ahcd->num_ports; temp++) + admhc_writel(ahcd, ADMHC_PS_CPP, + &ahcd->regs->portstatus[temp]); + } + /* flush those writes */ + admhc_writel_flush(ahcd); + + /* 2msec timelimit here means no irqs/preempt */ + spin_lock_irq(&ahcd->lock); + +retry: + admhc_writel(ahcd, ADMHC_CTRL_SR, &ahcd->regs->gencontrol); + temp = 30; /* ... allow extra time */ + while ((admhc_readl(ahcd, &ahcd->regs->gencontrol) & ADMHC_CTRL_SR) != 0) { + if (--temp == 0) { + spin_unlock_irq(&ahcd->lock); + admhc_err(ahcd, "USB HC reset timed out!\n"); + return -1; + } + udelay (1); } - return ret; -} + /* enable HOST mode, before access any host specific register */ + admhc_writel(ahcd, ADMHC_CTRL_UHFE, &ahcd->regs->gencontrol); -static void admhcd_stop(struct usb_hcd *hcd) -{ - struct admhcd *ahcd = hcd_to_admhcd(hcd); - unsigned long flags; - u32 val; + /* Tell the controller where the descriptor list is */ + admhc_writel(ahcd, (u32)ahcd->ed_head->dma, &ahcd->regs->hosthead); - spin_lock_irqsave(&ahcd->lock, flags); - admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0); + periodic_reinit(ahcd); - /* Set global control of power for ports */ - val = admhcd_reg_get(ahcd, ADMHCD_REG_RHDESCR); - val &= (~ADMHCD_PSM | ADMHCD_LPS); - admhcd_reg_set(ahcd, ADMHCD_REG_RHDESCR, val); + /* use rhsc irqs after khubd is fully initialized */ + hcd->poll_rh = 1; + hcd->uses_new_polling = 1; - spin_unlock_irqrestore(&ahcd->lock, flags); +#if 0 + /* wake on ConnectStatusChange, matching external hubs */ + admhc_writel(ahcd, RH_HS_DRWE, &ahcd->regs->roothub.status); +#else + /* FIXME roothub_write_status (ahcd, ADMHC_RH_DRWE); */ +#endif - /* Ask for software reset */ - admhcd_sw_reset(ahcd); -} + /* Choose the interrupts we care about now, others later on demand */ + admhc_intr_ack(ahcd, ~0); + admhc_intr_enable(ahcd, ADMHC_INTR_INIT); + admhc_writel(ahcd, ADMHC_RH_NPS | ADMHC_RH_LPSC, &ahcd->regs->rhdesc); -static struct hc_driver adm5120_hc_driver = { - .description = hcd_name, - .product_desc = "ADM5120 HCD", - .hcd_priv_size = sizeof(struct admhcd), - .irq = admhcd_irq, - .flags = HCD_USB11, - .urb_enqueue = admhcd_urb_enqueue, - .urb_dequeue = admhcd_urb_dequeue, - .endpoint_disable = admhcd_endpoint_disable, - .get_frame_number = admhcd_get_frame_number, - .hub_status_data = admhcd_hub_status_data, - .hub_control = admhcd_hub_control, - .start = admhcd_start, - .stop = admhcd_stop, - .reset = admhcd_reset, -}; - -#define resource_len(r) (((r)->end - (r)->start) + 1) - -static int __init adm5120hcd_probe(struct platform_device *pdev) -{ - struct usb_hcd *hcd; - struct admhcd *ahcd; - struct resource *data; - void __iomem *data_reg; + /* flush those writes */ + admhc_writel_flush(ahcd); - int err = 0, irq; + /* start controller operations */ + ahcd->host_control = ADMHC_BUSS_OPER; + admhc_writel(ahcd, ahcd->host_control, &ahcd->regs->host_control); - if (pdev->num_resources < 2) { - printk(KERN_WARNING PFX "not enough resources\n"); - err = -ENODEV; - goto out; + temp = 20; + while ((admhc_readl(ahcd, &ahcd->regs->host_control) + & ADMHC_HC_BUSS) != ADMHC_BUSS_OPER) { + if (--temp == 0) { + spin_unlock_irq(&ahcd->lock); + admhc_err(ahcd, "unable to setup operational mode!\n"); + return -1; + } + mdelay(1); } - irq = platform_get_irq(pdev, 0); - data = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->state = HC_STATE_RUNNING; - if (pdev->dev.dma_mask) { - printk(KERN_DEBUG PFX "no we won't dma\n"); - return -EINVAL; + ahcd->next_statechange = jiffies + STATECHANGE_DELAY; + +#if 0 + /* FIXME: enabling DMA is always failed here for an unknown reason */ + admhc_dma_enable(ahcd); + + temp = 200; + while ((admhc_readl(ahcd, &ahcd->regs->host_control) + & ADMHC_HC_DMAE) != ADMHC_HC_DMAE) { + if (--temp == 0) { + spin_unlock_irq(&ahcd->lock); + admhc_err(ahcd, "unable to enable DMA!\n"); + admhc_dump(ahcd, 1); + return -1; + } + mdelay(1); } - if (!data || irq < 0) { - printk(KERN_DEBUG PFX "either IRQ or data resource is invalid\n"); - err = -ENODEV; - goto out; +#endif + + spin_unlock_irq(&ahcd->lock); + + mdelay(ADMHC_POTPGT); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* an interrupt happens */ + +static irqreturn_t admhc_irq(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + struct admhcd_regs __iomem *regs = ahcd->regs; + u32 ints; + + ints = admhc_readl(ahcd, ®s->int_status); + if ((ints & ADMHC_INTR_INTA) == 0) { + /* no unmasked interrupt status is set */ + return IRQ_NONE; } - if (!request_mem_region(data->start, resource_len(data), hcd_name)) { - printk(KERN_DEBUG PFX "cannot request memory regions for the data resource\n"); - err = -EBUSY; - goto out; + ints &= admhc_readl(ahcd, ®s->int_enable); + + if (ints & ADMHC_INTR_FATI) { + /* e.g. due to PCI Master/Target Abort */ + admhc_disable(ahcd); + admhc_err(ahcd, "Fatal Error, controller disabled\n"); + admhc_dump(ahcd, 1); + admhc_usb_reset(ahcd); } - data_reg = ioremap(data->start, resource_len(data)); - if (data_reg == NULL) { - printk(KERN_DEBUG PFX "unable to ioremap\n"); - err = -ENOMEM; - goto out_mem; - } - - hcd = usb_create_hcd(&adm5120_hc_driver, &pdev->dev, pdev->dev.bus_id); - if (!hcd) { - printk(KERN_DEBUG PFX "unable to create the hcd\n"); - err = -ENOMEM; - goto out_unmap; + if (ints & ADMHC_INTR_INSM) { + admhc_vdbg(ahcd, "Root Hub Status Change\n"); + ahcd->next_statechange = jiffies + STATECHANGE_DELAY; + admhc_intr_ack(ahcd, ADMHC_INTR_RESI | ADMHC_INTR_INSM); + + /* NOTE: Vendors didn't always make the same implementation + * choices for RHSC. Many followed the spec; RHSC triggers + * on an edge, like setting and maybe clearing a port status + * change bit. With others it's level-triggered, active + * until khubd clears all the port status change bits. We'll + * always disable it here and rely on polling until khubd + * re-enables it. + */ + admhc_intr_disable(ahcd, ADMHC_INTR_INSM); + usb_hcd_poll_rh_status(hcd); + } else if (ints & ADMHC_INTR_RESI) { + /* For connect and disconnect events, we expect the controller + * to turn on RHSC along with RD. But for remote wakeup events + * this might not happen. + */ + admhc_vdbg(ahcd, "Resume Detect\n"); + admhc_intr_ack(ahcd, ADMHC_INTR_RESI); + hcd->poll_rh = 1; + if (ahcd->autostop) { + spin_lock(&ahcd->lock); + admhc_rh_resume(ahcd); + spin_unlock(&ahcd->lock); + } else + usb_hcd_resume_root_hub(hcd); } - hcd->rsrc_start = data->start; - hcd->rsrc_len = resource_len(data); - hcd->regs = data_reg; + if (ints & ADMHC_INTR_TDC) { + admhc_vdbg(ahcd, "Transfer Descriptor Complete\n"); + admhc_intr_ack(ahcd, ADMHC_INTR_TDC); + if (HC_IS_RUNNING(hcd->state)) + admhc_intr_disable(ahcd, ADMHC_INTR_TDC); + spin_lock(&ahcd->lock); + admhc_td_complete(ahcd); + spin_unlock(&ahcd->lock); + if (HC_IS_RUNNING(hcd->state)) + admhc_intr_enable(ahcd, ADMHC_INTR_TDC); + } - ahcd = hcd_to_admhcd(hcd); - ahcd->base = (u32)data_reg; + if (ints & ADMHC_INTR_SO) { + /* could track INTR_SO to reduce available PCI/... bandwidth */ + admhc_vdbg(ahcd, "Schedule Overrun\n"); + } - spin_lock_init(&ahcd->lock); - INIT_LIST_HEAD(&ahcd->async); + if (ints & ADMHC_INTR_BABI) { + admhc_intr_disable(ahcd, ADMHC_INTR_BABI); + admhc_intr_ack(ahcd, ADMHC_INTR_BABI); + admhc_err(ahcd, "Babble Detected\n"); + } - hcd->product_desc = "ADM5120 HCD"; +#if 1 + spin_lock(&ahcd->lock); + if (ahcd->ed_rm_list) + finish_unlinks(ahcd, admhc_frame_no(ahcd)); - err = usb_add_hcd(hcd, irq, IRQF_DISABLED); - if (err) { - printk(KERN_DEBUG PFX "unable to add hcd\n"); - goto out_dev; + if ((ints & ADMHC_INTR_SOFI) != 0 && !ahcd->ed_rm_list + && HC_IS_RUNNING(hcd->state)) + admhc_intr_disable(ahcd, ADMHC_INTR_SOFI); + spin_unlock(&ahcd->lock); +#else + if (ints & ADMHC_INTR_SOFI) { + admhc_vdbg(ahcd, "Start Of Frame\n"); + spin_lock(&ahcd->lock); + + /* handle any pending ED removes */ + finish_unlinks(ahcd, admhc_frameno(ahcd)); + + /* leaving INTR_SOFI enabled when there's still unlinking + * to be done in the (next frame). + */ + if ((ahcd->ed_rm_list == NULL) || + HC_IS_RUNNING(hcd->state) == 0) + /* + * disable INTR_SOFI if there are no unlinking to be + * done (in the next frame) + */ + admhc_intr_disable(ahcd, ADMHC_INTR_SOFI); + + spin_unlock(&ahcd->lock); } +#endif - return 0; + if (HC_IS_RUNNING(hcd->state)) { + admhc_intr_ack(ahcd, ints); + admhc_intr_enable(ahcd, ADMHC_INTR_MIE); + admhc_writel_flush(ahcd); + } -out_dev: - usb_put_hcd(hcd); -out_unmap: - iounmap(data_reg); -out_mem: - release_mem_region(data->start, resource_len(data)); -out: - return err; + return IRQ_HANDLED; } -#ifdef CONFIG_PM -static int adm5120hcd_suspend(struct platform_device *pdev, pm_message_t state) +/*-------------------------------------------------------------------------*/ + +static void admhc_stop(struct usb_hcd *hcd) { - pdev->dev.power.power_state = state; - mdelay(1); - return 0; + struct admhcd *ahcd = hcd_to_admhcd(hcd); + + admhc_dump(ahcd, 1); + + flush_scheduled_work(); + + admhc_usb_reset(ahcd); + admhc_intr_disable(ahcd, ADMHC_INTR_MIE); + + free_irq(hcd->irq, hcd); + hcd->irq = -1; + + remove_debug_files(ahcd); + admhc_eds_cleanup(ahcd); + admhc_mem_cleanup(ahcd); } -static int adm5120hcd_resume(struct platform_device *pdev, pm_message_t state) +/*-------------------------------------------------------------------------*/ + +/* must not be called from interrupt context */ + +#ifdef CONFIG_PM + +static int admhc_restart(struct admhcd *ahcd) { - pdev->dev.power.power_state = PMSG_ON; - mdelay(1); + int temp; + int i; + struct urb_priv *priv; + + /* mark any devices gone, so they do nothing till khubd disconnects. + * recycle any "live" eds/tds (and urbs) right away. + * later, khubd disconnect processing will recycle the other state, + * (either as disconnect/reconnect, or maybe someday as a reset). + */ + spin_lock_irq(&ahcd->lock); + admhc_disable(ahcd); + usb_root_hub_lost_power(admhcd_to_hcd(ahcd)->self.root_hub); + if (!list_empty(&ahcd->pending)) + admhc_dbg(ahcd, "abort schedule...\n"); + list_for_each_entry (priv, &ahcd->pending, pending) { + struct urb *urb = priv->td[0]->urb; + struct ed *ed = priv->ed; + + switch (ed->state) { + case ED_OPER: + ed->state = ED_UNLINK; + ed->hwINFO |= cpu_to_hc32(ahcd, ED_DEQUEUE); + ed_deschedule (ahcd, ed); + + ed->ed_next = ahcd->ed_rm_list; + ed->ed_prev = NULL; + ahcd->ed_rm_list = ed; + /* FALLTHROUGH */ + case ED_UNLINK: + break; + default: + admhc_dbg(ahcd, "bogus ed %p state %d\n", + ed, ed->state); + } + + spin_lock(&urb->lock); + urb->status = -ESHUTDOWN; + spin_unlock(&urb->lock); + } + finish_unlinks (ahcd, 0); + spin_unlock_irq(&ahcd->lock); + + /* paranoia, in case that didn't work: */ + + /* empty the interrupt branches */ + for (i = 0; i < NUM_INTS; i++) ahcd->load[i] = 0; + for (i = 0; i < NUM_INTS; i++) ahcd->hcca->int_table[i] = 0; + + /* no EDs to remove */ + ahcd->ed_rm_list = NULL; + + /* empty control and bulk lists */ + ahcd->ed_controltail = NULL; + ahcd->ed_bulktail = NULL; + + if ((temp = admhc_run(ahcd)) < 0) { + admhc_err(ahcd, "can't restart, %d\n", temp); + return temp; + } else { + /* here we "know" root ports should always stay powered, + * and that if we try to turn them back on the root hub + * will respond to CSC processing. + */ + i = ahcd->num_ports; + while (i--) + admhc_writel(ahcd, RH_PS_PSS, + &ahcd->regs->portstatus[i]); + admhc_dbg(ahcd, "restart complete\n"); + } return 0; } -#else -#define adm5120hcd_suspend NULL -#define adm5120hcd_resume NULL #endif -static int __init_or_module adm5120hcd_remove(struct platform_device *pdev) -{ - struct usb_hcd *hcd = platform_get_drvdata(pdev); - struct admhcd *ahcd; +/*-------------------------------------------------------------------------*/ - if (!hcd) - return 0; - ahcd = hcd_to_admhcd(hcd); - usb_remove_hcd(hcd); +#ifdef CONFIG_MIPS_ADM5120 +#include "adm5120-drv.c" +#define PLATFORM_DRIVER usb_hcd_adm5120_driver +#endif - usb_put_hcd(hcd); - return 0; -} +#if !defined(PLATFORM_DRIVER) +#error "missing bus glue for admhc-hcd" +#endif + +#define DRIVER_INFO DRIVER_DESC " " DRIVER_VERSION -static struct platform_driver adm5120hcd_driver = { - .probe = adm5120hcd_probe, - .remove = adm5120hcd_remove, - .suspend = adm5120hcd_suspend, - .remove = adm5120hcd_resume, - .driver = { - .name = (char *)hcd_name, - .owner = THIS_MODULE, - }, -}; - -static int __init adm5120hcd_init(void) +static int __init admhc_hcd_mod_init(void) { - int ret; + int retval = 0; - if (usb_disabled()) { - printk(KERN_DEBUG PFX "USB support is disabled\n"); + if (usb_disabled()) return -ENODEV; - } - if (mips_machgroup != MACH_GROUP_ADM5120) { - printk(KERN_DEBUG PFX "unsupported machine group\n"); - return -ENODEV; - } + pr_info("%s: " DRIVER_INFO "\n", hcd_name); + pr_info("%s: block sizes: ed %Zd td %Zd\n", hcd_name, + sizeof (struct ed), sizeof (struct td)); - ret = platform_driver_register(&adm5120hcd_driver); - if (ret == 0) - printk(KERN_INFO PFX "registered\n"); +#ifdef PLATFORM_DRIVER + retval = platform_driver_register(&PLATFORM_DRIVER); + if (retval < 0) + goto error_platform; +#endif - return ret; + return retval; + +#ifdef PLATFORM_DRIVER + platform_driver_unregister(&PLATFORM_DRIVER); +error_platform: +#endif + return retval; } +module_init(admhc_hcd_mod_init); -static void __exit adm5120hcd_exit(void) +static void __exit admhc_hcd_mod_exit(void) { - platform_driver_unregister(&adm5120hcd_driver); - printk(KERN_INFO PFX "driver unregistered\n"); + platform_driver_unregister(&PLATFORM_DRIVER); } +module_exit(admhc_hcd_mod_exit); -module_init(adm5120hcd_init); -module_exit(adm5120hcd_exit); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_LICENSE("GPL"); diff --git a/target/linux/adm5120/files/drivers/usb/host/adm5120-hub.c b/target/linux/adm5120/files/drivers/usb/host/adm5120-hub.c new file mode 100644 index 000000000..dc1a4a52d --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-hub.c @@ -0,0 +1,770 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net> + * + * This file is licenced under GPL + */ + +/*-------------------------------------------------------------------------*/ + +/* + * OHCI Root Hub ... the nonsharable stuff + */ + +#define dbg_port(hc,label,num,value) \ + admhc_dbg(hc, \ + "%s port%d " \ + "= 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \ + label, num, temp, \ + (temp & ADMHC_PS_PRSC) ? " PRSC" : "", \ + (temp & ADMHC_PS_OCIC) ? " OCIC" : "", \ + (temp & ADMHC_PS_PSSC) ? " PSSC" : "", \ + (temp & ADMHC_PS_PESC) ? " PESC" : "", \ + (temp & ADMHC_PS_CSC) ? " CSC" : "", \ + \ + (temp & ADMHC_PS_LSDA) ? " LSDA" : "", \ + (temp & ADMHC_PS_PPS) ? " PPS" : "", \ + (temp & ADMHC_PS_PRS) ? " PRS" : "", \ + (temp & ADMHC_PS_POCI) ? " POCI" : "", \ + (temp & ADMHC_PS_PSS) ? " PSS" : "", \ + \ + (temp & ADMHC_PS_PES) ? " PES" : "", \ + (temp & ADMHC_PS_CCS) ? " CCS" : "" \ + ); + +#define dbg_port_write(hc,label,num,value) \ + admhc_dbg(hc, \ + "%s port%d " \ + "= 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \ + label, num, temp, \ + (temp & ADMHC_PS_PRSC) ? " PRSC" : "", \ + (temp & ADMHC_PS_OCIC) ? " OCIC" : "", \ + (temp & ADMHC_PS_PSSC) ? " PSSC" : "", \ + (temp & ADMHC_PS_PESC) ? " PESC" : "", \ + (temp & ADMHC_PS_CSC) ? " CSC" : "", \ + \ + (temp & ADMHC_PS_CPP) ? " CPP" : "", \ + (temp & ADMHC_PS_SPP) ? " SPP" : "", \ + (temp & ADMHC_PS_SPR) ? " SPR" : "", \ + (temp & ADMHC_PS_CPS) ? " CPS" : "", \ + (temp & ADMHC_PS_SPS) ? " SPS" : "", \ + \ + (temp & ADMHC_PS_SPE) ? " SPE" : "", \ + (temp & ADMHC_PS_CPE) ? " CPE" : "" \ + ); + +/*-------------------------------------------------------------------------*/ + +/* hcd->hub_irq_enable() */ +static void admhc_rhsc_enable(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + + spin_lock_irq(&ahcd->lock); + if (!ahcd->autostop) + del_timer(&hcd->rh_timer); /* Prevent next poll */ + admhc_intr_enable(ahcd, ADMHC_INTR_INSM); + spin_unlock_irq(&ahcd->lock); +} + +#define OHCI_SCHED_ENABLES \ + (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE) + +#ifdef CONFIG_PM +static int admhc_restart(struct admhcd *ahcd); + +static int admhc_rh_suspend(struct admhcd *ahcd, int autostop) +__releases(ahcd->lock) +__acquires(ahcd->lock) +{ + int status = 0; + + ahcd->hc_control = admhc_readl(ahcd, &ahcd->regs->control); + switch (ahcd->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_RESUME: + admhc_dbg(ahcd, "resume/suspend?\n"); + ahcd->hc_control &= ~OHCI_CTRL_HCFS; + ahcd->hc_control |= OHCI_USB_RESET; + admhc_writel(ahcd, ahcd->hc_control, &ahcd->ahcd->regs->control); + (void) admhc_readl(ahcd, &ahcd->regs->control); + /* FALL THROUGH */ + case OHCI_USB_RESET: + status = -EBUSY; + admhc_dbg(ahcd, "needs reinit!\n"); + goto done; + case OHCI_USB_SUSPEND: + if (!ahcd->autostop) { + admhc_dbg(ahcd, "already suspended\n"); + goto done; + } + } + admhc_dbg(ahcd, "%s root hub\n", + autostop ? "auto-stop" : "suspend"); + + /* First stop any processing */ + if (!autostop && (ahcd->hc_control & OHCI_SCHED_ENABLES)) { + ahcd->hc_control &= ~OHCI_SCHED_ENABLES; + admhc_writel(ahcd, ahcd->hc_control, &ahcd->ahcd->regs->control); + ahcd->hc_control = admhc_readl(ahcd, &ahcd->regs->control); + admhc_writel(ahcd, OHCI_INTR_SF, &ahcd->regs->intrstatus); + + /* sched disables take effect on the next frame, + * then the last WDH could take 6+ msec + */ + admhc_dbg(ahcd, "stopping schedules ...\n"); + ahcd->autostop = 0; + spin_unlock_irq (&ahcd->lock); + msleep (8); + spin_lock_irq (&ahcd->lock); + } + dl_done_list (ahcd); + finish_unlinks (ahcd, admhc_frame_no(ahcd)); + + /* maybe resume can wake root hub */ + if (device_may_wakeup(&admhcd_to_hcd(ahcd)->self.root_hub->dev) || + autostop) + ahcd->hc_control |= OHCI_CTRL_RWE; + else { + admhc_writel(ahcd, OHCI_INTR_RHSC, &ahcd->regs->intrdisable); + ahcd->hc_control &= ~OHCI_CTRL_RWE; + } + + /* Suspend hub ... this is the "global (to this bus) suspend" mode, + * which doesn't imply ports will first be individually suspended. + */ + ahcd->hc_control &= ~OHCI_CTRL_HCFS; + ahcd->hc_control |= OHCI_USB_SUSPEND; + admhc_writel(ahcd, ahcd->hc_control, &ahcd->ahcd->regs->control); + (void) admhc_readl(ahcd, &ahcd->regs->control); + + /* no resumes until devices finish suspending */ + if (!autostop) { + ahcd->next_statechange = jiffies + msecs_to_jiffies (5); + ahcd->autostop = 0; + } + +done: + return status; +} + +static inline struct ed *find_head(struct ed *ed) +{ + /* for bulk and control lists */ + while (ed->ed_prev) + ed = ed->ed_prev; + return ed; +} + +/* caller has locked the root hub */ +static int admhc_rh_resume(struct admhcd *ahcd) +__releases(ahcd->lock) +__acquires(ahcd->lock) +{ + struct usb_hcd *hcd = admhcd_to_hcd (ahcd); + u32 temp, enables; + int status = -EINPROGRESS; + int autostopped = ahcd->autostop; + + ahcd->autostop = 0; + ahcd->hc_control = admhc_readl(ahcd, &ahcd->regs->control); + + if (ahcd->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) { + /* this can happen after resuming a swsusp snapshot */ + if (hcd->state == HC_STATE_RESUMING) { + admhc_dbg(ahcd, "BIOS/SMM active, control %03x\n", + ahcd->hc_control); + status = -EBUSY; + /* this happens when pmcore resumes HC then root */ + } else { + admhc_dbg(ahcd, "duplicate resume\n"); + status = 0; + } + } else switch (ahcd->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_SUSPEND: + ahcd->hc_control &= ~(OHCI_CTRL_HCFS|OHCI_SCHED_ENABLES); + ahcd->hc_control |= OHCI_USB_RESUME; + admhc_writel(ahcd, ahcd->hc_control, &ahcd->ahcd->regs->control); + (void) admhc_readl(ahcd, &ahcd->regs->control); + admhc_dbg(ahcd, "%s root hub\n", + autostopped ? "auto-start" : "resume"); + break; + case OHCI_USB_RESUME: + /* HCFS changes sometime after INTR_RD */ + admhc_dbg(ahcd, "%swakeup root hub\n", + autostopped ? "auto-" : ""); + break; + case OHCI_USB_OPER: + /* this can happen after resuming a swsusp snapshot */ + admhc_dbg(ahcd, "snapshot resume? reinit\n"); + status = -EBUSY; + break; + default: /* RESET, we lost power */ + admhc_dbg(ahcd, "lost power\n"); + status = -EBUSY; + } + if (status == -EBUSY) { + if (!autostopped) { + spin_unlock_irq (&ahcd->lock); + (void) ahcd_init (ahcd); + status = admhc_restart (ahcd); + spin_lock_irq (&ahcd->lock); + } + return status; + } + if (status != -EINPROGRESS) + return status; + if (autostopped) + goto skip_resume; + spin_unlock_irq (&ahcd->lock); + + /* Some controllers (lucent erratum) need extra-long delays */ + msleep (20 /* usb 11.5.1.10 */ + 12 /* 32 msec counter */ + 1); + + temp = admhc_readl(ahcd, &ahcd->regs->control); + temp &= OHCI_CTRL_HCFS; + if (temp != OHCI_USB_RESUME) { + admhc_err (ahcd, "controller won't resume\n"); + spin_lock_irq(&ahcd->lock); + return -EBUSY; + } + + /* disable old schedule state, reinit from scratch */ + admhc_writel(ahcd, 0, &ahcd->regs->ed_controlhead); + admhc_writel(ahcd, 0, &ahcd->regs->ed_controlcurrent); + admhc_writel(ahcd, 0, &ahcd->regs->ed_bulkhead); + admhc_writel(ahcd, 0, &ahcd->regs->ed_bulkcurrent); + admhc_writel(ahcd, 0, &ahcd->regs->ed_periodcurrent); + admhc_writel(ahcd, (u32) ahcd->hcca_dma, &ahcd->ahcd->regs->hcca); + + /* Sometimes PCI D3 suspend trashes frame timings ... */ + periodic_reinit(ahcd); + + /* the following code is executed with ahcd->lock held and + * irqs disabled if and only if autostopped is true + */ + +skip_resume: + /* interrupts might have been disabled */ + admhc_writel(ahcd, OHCI_INTR_INIT, &ahcd->regs->int_enable); + if (ahcd->ed_rm_list) + admhc_writel(ahcd, OHCI_INTR_SF, &ahcd->regs->int_enable); + + /* Then re-enable operations */ + admhc_writel(ahcd, OHCI_USB_OPER, &ahcd->regs->control); + (void) admhc_readl(ahcd, &ahcd->regs->control); + if (!autostopped) + msleep (3); + + temp = ahcd->hc_control; + temp &= OHCI_CTRL_RWC; + temp |= OHCI_CONTROL_INIT | OHCI_USB_OPER; + ahcd->hc_control = temp; + admhc_writel(ahcd, temp, &ahcd->regs->control); + (void) admhc_readl(ahcd, &ahcd->regs->control); + + /* TRSMRCY */ + if (!autostopped) { + msleep (10); + spin_lock_irq (&ahcd->lock); + } + /* now ahcd->lock is always held and irqs are always disabled */ + + /* keep it alive for more than ~5x suspend + resume costs */ + ahcd->next_statechange = jiffies + STATECHANGE_DELAY; + + /* maybe turn schedules back on */ + enables = 0; + temp = 0; + if (!ahcd->ed_rm_list) { + if (ahcd->ed_controltail) { + admhc_writel(ahcd, + find_head (ahcd->ed_controltail)->dma, + &ahcd->regs->ed_controlhead); + enables |= OHCI_CTRL_CLE; + temp |= OHCI_CLF; + } + if (ahcd->ed_bulktail) { + admhc_writel(ahcd, find_head (ahcd->ed_bulktail)->dma, + &ahcd->regs->ed_bulkhead); + enables |= OHCI_CTRL_BLE; + temp |= OHCI_BLF; + } + } + if (hcd->self.bandwidth_isoc_reqs || hcd->self.bandwidth_int_reqs) + enables |= OHCI_CTRL_PLE|OHCI_CTRL_IE; + if (enables) { + admhc_dbg(ahcd, "restarting schedules ... %08x\n", enables); + ahcd->hc_control |= enables; + admhc_writel(ahcd, ahcd->hc_control, &ahcd->ahcd->regs->control); + if (temp) + admhc_writel(ahcd, temp, &ahcd->regs->cmdstatus); + (void) admhc_readl(ahcd, &ahcd->regs->control); + } + + return 0; +} + +static int admhc_bus_suspend(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + int rc; + + spin_lock_irq(&ahcd->lock); + + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) + rc = -ESHUTDOWN; + else + rc = admhc_rh_suspend (ahcd, 0); + spin_unlock_irq(&ahcd->lock); + return rc; +} + +static int admhc_bus_resume(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + int rc; + + if (time_before(jiffies, ahcd->next_statechange)) + msleep(5); + + spin_lock_irq (&ahcd->lock); + + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) + rc = -ESHUTDOWN; + else + rc = admhc_rh_resume (ahcd); + spin_unlock_irq(&ahcd->lock); + + /* poll until we know a device is connected or we autostop */ + if (rc == 0) + usb_hcd_poll_rh_status(hcd); + return rc; +} + +/* Carry out polling-, autostop-, and autoresume-related state changes */ +static int admhc_root_hub_state_changes(struct admhcd *ahcd, int changed, + int any_connected) +{ + int poll_rh = 1; + + switch (ahcd->hc_control & OHCI_CTRL_HCFS) { + + case OHCI_USB_OPER: + /* keep on polling until we know a device is connected + * and RHSC is enabled */ + if (!ahcd->autostop) { + if (any_connected || + !device_may_wakeup(&admhcd_to_hcd(ahcd) + ->self.root_hub->dev)) { + if (admhc_readl(ahcd, &ahcd->regs->int_enable) & + OHCI_INTR_RHSC) + poll_rh = 0; + } else { + ahcd->autostop = 1; + ahcd->next_statechange = jiffies + HZ; + } + + /* if no devices have been attached for one second, autostop */ + } else { + if (changed || any_connected) { + ahcd->autostop = 0; + ahcd->next_statechange = jiffies + + STATECHANGE_DELAY; + } else if (time_after_eq(jiffies, + ahcd->next_statechange) + && !ahcd->ed_rm_list + && !(ahcd->hc_control & + OHCI_SCHED_ENABLES)) { + ahcd_rh_suspend(ahcd, 1); + } + } + break; + + /* if there is a port change, autostart or ask to be resumed */ + case OHCI_USB_SUSPEND: + case OHCI_USB_RESUME: + if (changed) { + if (ahcd->autostop) + admhc_rh_resume(ahcd); + else + usb_hcd_resume_root_hub(admhcd_to_hcd(ahcd)); + } else { + /* everything is idle, no need for polling */ + poll_rh = 0; + } + break; + } + return poll_rh; +} + +#else /* CONFIG_PM */ + +static inline int admhc_rh_resume(struct admhcd *ahcd) +{ + return 0; +} + +/* Carry out polling-related state changes. + * autostop isn't used when CONFIG_PM is turned off. + */ +static int admhc_root_hub_state_changes(struct admhcd *ahcd, int changed, + int any_connected) +{ + int poll_rh = 1; + + /* keep on polling until RHSC is enabled */ + if (admhc_readl(ahcd, &ahcd->regs->int_enable) & ADMHC_INTR_INSM) + poll_rh = 0; + + return poll_rh; +} + +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int +admhc_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + int i, changed = 0, length = 1; + int any_connected = 0; + unsigned long flags; + u32 status; + + spin_lock_irqsave(&ahcd->lock, flags); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) + goto done; + + /* init status */ + status = admhc_get_rhdesc(ahcd); + if (status & (ADMHC_RH_LPSC | ADMHC_RH_OCIC)) + buf [0] = changed = 1; + else + buf [0] = 0; + if (ahcd->num_ports > 7) { + buf [1] = 0; + length++; + } + + /* look at each port */ + for (i = 0; i < ahcd->num_ports; i++) { + status = admhc_get_portstatus(ahcd, i); + + /* can't autostop if ports are connected */ + any_connected |= (status & ADMHC_PS_CCS); + + if (status & (ADMHC_PS_CSC | ADMHC_PS_PESC | ADMHC_PS_PSSC + | ADMHC_PS_OCIC | ADMHC_PS_PRSC)) { + changed = 1; + if (i < 7) + buf [0] |= 1 << (i + 1); + else + buf [1] |= 1 << (i - 7); + } + } + + hcd->poll_rh = admhc_root_hub_state_changes(ahcd, changed, + any_connected); + +done: + spin_unlock_irqrestore(&ahcd->lock, flags); + + return changed ? length : 0; +} + +/*-------------------------------------------------------------------------*/ + +static void admhc_hub_descriptor(struct admhcd *ahcd, + struct usb_hub_descriptor *desc) +{ + u32 rh = admhc_get_rhdesc(ahcd); + u16 temp; + + desc->bDescriptorType = USB_DT_HUB; /* Hub-descriptor */ + desc->bPwrOn2PwrGood = ADMHC_POTPGT/2; /* use default value */ + desc->bHubContrCurrent = 0x00; /* 0mA */ + + desc->bNbrPorts = ahcd->num_ports; + temp = 1 + (ahcd->num_ports / 8); + desc->bDescLength = USB_DT_HUB_NONVAR_SIZE + 2 * temp; + + /* FIXME */ + temp = 0; + if (rh & ADMHC_RH_NPS) /* no power switching? */ + temp |= 0x0002; + if (rh & ADMHC_RH_PSM) /* per-port power switching? */ + temp |= 0x0001; + if (rh & ADMHC_RH_NOCP) /* no overcurrent reporting? */ + temp |= 0x0010; + else if (rh & ADMHC_RH_OCPM) /* per-port overcurrent reporting? */ + temp |= 0x0008; + desc->wHubCharacteristics = (__force __u16)cpu_to_hc16(ahcd, temp); + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + desc->bitmap [0] = 0; + desc->bitmap [0] = ~0; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_OTG + +static int admhc_start_port_reset(struct usb_hcd *hcd, unsigned port) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + u32 status; + + if (!port) + return -EINVAL; + port--; + + /* start port reset before HNP protocol times out */ + status = admhc_readl(ahcd, &ahcd->regs->portstatus[port]); + if (!(status & ADMHC_PS_CCS)) + return -ENODEV; + + /* khubd will finish the reset later */ + admhc_writel(ahcd, ADMHC_PS_PRS, &ahcd->regs->portstatus[port]); + return 0; +} + +static void start_hnp(struct admhcd *ahcd); + +#else + +#define admhc_start_port_reset NULL + +#endif + +/*-------------------------------------------------------------------------*/ + + +/* See usb 7.1.7.5: root hubs must issue at least 50 msec reset signaling, + * not necessarily continuous ... to guard against resume signaling. + * The short timeout is safe for non-root hubs, and is backward-compatible + * with earlier Linux hosts. + */ +#ifdef CONFIG_USB_SUSPEND +#define PORT_RESET_MSEC 50 +#else +#define PORT_RESET_MSEC 10 +#endif + +/* this timer value might be vendor-specific ... */ +#define PORT_RESET_HW_MSEC 10 + +/* wrap-aware logic morphed from <linux/jiffies.h> */ +#define tick_before(t1,t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0) + +/* called from some task, normally khubd */ +static inline int root_port_reset(struct admhcd *ahcd, unsigned port) +{ +#if 0 + /* FIXME: revert to this when frame numbers are updated */ + __hc32 __iomem *portstat = &ahcd->regs->portstatus[port]; + u32 temp; + u16 now = admhc_readl(ahcd, &ahcd->regs->fmnumber); + u16 reset_done = now + PORT_RESET_MSEC; + + /* build a "continuous enough" reset signal, with up to + * 3msec gap between pulses. scheduler HZ==100 must work; + * this might need to be deadline-scheduled. + */ + do { + /* spin until any current reset finishes */ + for (;;) { + temp = admhc_readl(ahcd, portstat); + /* handle e.g. CardBus eject */ + if (temp == ~(u32)0) + return -ESHUTDOWN; + if (!(temp & ADMHC_PS_PRS)) + break; + udelay (500); + } + + if (!(temp & ADMHC_PS_CCS)) + break; + if (temp & ADMHC_PS_PRSC) + admhc_writel(ahcd, ADMHC_PS_PRSC, portstat); + + /* start the next reset, sleep till it's probably done */ + admhc_writel(ahcd, ADMHC_PS_PRS, portstat); + msleep(PORT_RESET_HW_MSEC); + now = admhc_readl(ahcd, &ahcd->regs->fmnumber); + } while (tick_before(now, reset_done)); + /* caller synchronizes using PRSC */ +#else + __hc32 __iomem *portstat = &ahcd->regs->portstatus[port]; + u32 temp; + unsigned long reset_done = jiffies + msecs_to_jiffies(PORT_RESET_MSEC); + + /* build a "continuous enough" reset signal, with up to + * 3msec gap between pulses. scheduler HZ==100 must work; + * this might need to be deadline-scheduled. + */ + do { + /* spin until any current reset finishes */ + for (;;) { + temp = admhc_readl(ahcd, portstat); + /* handle e.g. CardBus eject */ + if (temp == ~(u32)0) + return -ESHUTDOWN; + if (!(temp & ADMHC_PS_PRS)) + break; + udelay (500); + } + + if (!(temp & ADMHC_PS_CCS)) + break; + + if (temp & ADMHC_PS_PRSC) + admhc_writel(ahcd, ADMHC_PS_PRSC, portstat); + + /* start the next reset, sleep till it's probably done */ + admhc_writel(ahcd, ADMHC_PS_PRS, portstat); + msleep(PORT_RESET_HW_MSEC); + } while (time_before(jiffies, reset_done)); + + admhc_writel(ahcd, ADMHC_PS_SPE | ADMHC_PS_CSC, portstat); + msleep(100); +#endif + return 0; +} + +static int admhc_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct admhcd *ahcd = hcd_to_admhcd(hcd); + int ports = hcd_to_bus (hcd)->root_hub->maxchild; + u32 temp; + int retval = 0; + + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) + return -ESHUTDOWN; + + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: +#if 0 /* FIXME */ + admhc_writel(ahcd, ADMHC_RH_OCIC, + &ahcd->regs->roothub.status); +#endif + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + temp = ADMHC_PS_CPE; + break; + case USB_PORT_FEAT_SUSPEND: + temp = ADMHC_PS_CPS; + break; + case USB_PORT_FEAT_POWER: + temp = ADMHC_PS_CPP; + break; + case USB_PORT_FEAT_C_CONNECTION: + temp = ADMHC_PS_CSC; + break; + case USB_PORT_FEAT_C_ENABLE: + temp = ADMHC_PS_PESC; + break; + case USB_PORT_FEAT_C_SUSPEND: + temp = ADMHC_PS_PSSC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + temp = ADMHC_PS_OCIC; + break; + case USB_PORT_FEAT_C_RESET: + temp = ADMHC_PS_PRSC; + break; + default: + goto error; + } + admhc_writel(ahcd, temp, &ahcd->regs->portstatus[wIndex]); + break; + case GetHubDescriptor: + admhc_hub_descriptor(ahcd, (struct usb_hub_descriptor *) buf); + break; + case GetHubStatus: + temp = admhc_get_rhdesc(ahcd); + temp &= ~(ADMHC_RH_CRWE | ADMHC_RH_DRWE); + put_unaligned(cpu_to_le32 (temp), (__le32 *) buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = admhc_get_portstatus(ahcd, wIndex); + put_unaligned(cpu_to_le32 (temp), (__le32 *) buf); + + dbg_port(ahcd, "GetPortStatus", wIndex, temp); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + // FIXME: this can be cleared, yes? + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case SetPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + admhc_writel(ahcd, ADMHC_PS_SPE, + &ahcd->regs->portstatus[wIndex]); + break; + case USB_PORT_FEAT_SUSPEND: +#ifdef CONFIG_USB_OTG + if (hcd->self.otg_port == (wIndex + 1) + && hcd->self.b_hnp_enable) + start_hnp(ahcd); + else +#endif + admhc_writel(ahcd, ADMHC_PS_SPS, + &ahcd->regs->portstatus[wIndex]); + break; + case USB_PORT_FEAT_POWER: + admhc_writel(ahcd, ADMHC_PS_SPP, + &ahcd->regs->portstatus[wIndex]); + break; + case USB_PORT_FEAT_RESET: + retval = root_port_reset(ahcd, wIndex); + break; + default: + goto error; + } + break; + + default: +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + return retval; +} + diff --git a/target/linux/adm5120/files/drivers/usb/host/adm5120-mem.c b/target/linux/adm5120/files/drivers/usb/host/adm5120-mem.c new file mode 100644 index 000000000..924221be1 --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-mem.c @@ -0,0 +1,196 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * + * This file is licenced under the GPL. + */ + +/*-------------------------------------------------------------------------*/ + +/* + * OHCI deals with three types of memory: + * - data used only by the HCD ... kmalloc is fine + * - async and periodic schedules, shared by HC and HCD ... these + * need to use dma_pool or dma_alloc_coherent + * - driver buffers, read/written by HC ... the hcd glue or the + * device driver provides us with dma addresses + * + * There's also "register" data, which is memory mapped. + * No memory seen by this driver (or any HCD) may be paged out. + */ + +/*-------------------------------------------------------------------------*/ + +static void admhc_hcd_init(struct admhcd *ahcd) +{ + ahcd->next_statechange = jiffies; + spin_lock_init(&ahcd->lock); + INIT_LIST_HEAD(&ahcd->pending); +} + +/*-------------------------------------------------------------------------*/ + +static int admhc_mem_init(struct admhcd *ahcd) +{ + ahcd->td_cache = dma_pool_create("admhc_td", + admhcd_to_hcd(ahcd)->self.controller, + sizeof(struct td), + TD_ALIGN, /* byte alignment */ + 0 /* no page-crossing issues */ + ); + if (!ahcd->td_cache) + goto err; + + ahcd->ed_cache = dma_pool_create("admhc_ed", + admhcd_to_hcd(ahcd)->self.controller, + sizeof(struct ed), + ED_ALIGN, /* byte alignment */ + 0 /* no page-crossing issues */ + ); + if (!ahcd->ed_cache) + goto err_td_cache; + + return 0; + +err_td_cache: + dma_pool_destroy(ahcd->td_cache); + ahcd->td_cache = NULL; +err: + return -ENOMEM; +} + +static void admhc_mem_cleanup(struct admhcd *ahcd) +{ + if (ahcd->td_cache) { + dma_pool_destroy(ahcd->td_cache); + ahcd->td_cache = NULL; + } + + if (ahcd->ed_cache) { + dma_pool_destroy(ahcd->ed_cache); + ahcd->ed_cache = NULL; + } +} + +/*-------------------------------------------------------------------------*/ + +/* ahcd "done list" processing needs this mapping */ +static inline struct td *dma_to_td(struct admhcd *ahcd, dma_addr_t td_dma) +{ + struct td *td; + + td_dma &= TD_MASK; + td = ahcd->td_hash[TD_HASH_FUNC(td_dma)]; + while (td && td->td_dma != td_dma) + td = td->td_hash; + + return td; +} + +/* TDs ... */ +static struct td *td_alloc(struct admhcd *ahcd, gfp_t mem_flags) +{ + dma_addr_t dma; + struct td *td; + + td = dma_pool_alloc(ahcd->td_cache, mem_flags, &dma); + if (!td) + return NULL; + + /* in case ahcd fetches it, make it look dead */ + memset(td, 0, sizeof *td); + td->hwNextTD = cpu_to_hc32(ahcd, dma); + td->td_dma = dma; + /* hashed in td_fill */ + + return td; +} + +static void td_free(struct admhcd *ahcd, struct td *td) +{ + struct td **prev = &ahcd->td_hash[TD_HASH_FUNC(td->td_dma)]; + + while (*prev && *prev != td) + prev = &(*prev)->td_hash; + if (*prev) + *prev = td->td_hash; +#if 0 + /* TODO: remove */ + else if ((td->hwINFO & cpu_to_hc32(ahcd, TD_DONE)) != 0) + admhc_dbg (ahcd, "no hash for td %p\n", td); +#else + else if ((td->flags & TD_FLAG_DONE) != 0) + admhc_dbg (ahcd, "no hash for td %p\n", td); +#endif + dma_pool_free(ahcd->td_cache, td, td->td_dma); +} + +/*-------------------------------------------------------------------------*/ + +/* EDs ... */ +static struct ed *ed_alloc(struct admhcd *ahcd, gfp_t mem_flags) +{ + dma_addr_t dma; + struct ed *ed; + + ed = dma_pool_alloc(ahcd->ed_cache, mem_flags, &dma); + if (!ed) + return NULL; + + memset(ed, 0, sizeof(*ed)); + ed->dma = dma; + + INIT_LIST_HEAD(&ed->td_list); + INIT_LIST_HEAD(&ed->urb_list); + + return ed; +} + +static void ed_free(struct admhcd *ahcd, struct ed *ed) +{ + dma_pool_free(ahcd->ed_cache, ed, ed->dma); +} + +/*-------------------------------------------------------------------------*/ + +/* URB priv ... */ +static void urb_priv_free(struct admhcd *ahcd, struct urb_priv *urb_priv) +{ + int i; + + for (i = 0; i < urb_priv->td_cnt; i++) + if (urb_priv->td[i]) + td_free(ahcd, urb_priv->td[i]); + + list_del(&urb_priv->pending); + kfree(urb_priv); +} + +static struct urb_priv *urb_priv_alloc(struct admhcd *ahcd, int num_tds, + gfp_t mem_flags) +{ + struct urb_priv *priv; + + /* allocate the private part of the URB */ + priv = kzalloc(sizeof(*priv) + sizeof(struct td) * num_tds, mem_flags); + if (!priv) + goto err; + + /* allocate the TDs (deferring hash chain updates) */ + for (priv->td_cnt = 0; priv->td_cnt < num_tds; priv->td_cnt++) { + priv->td[priv->td_cnt] = td_alloc(ahcd, mem_flags); + if (priv->td[priv->td_cnt] == NULL) + goto err_free; + } + + INIT_LIST_HEAD(&priv->pending); + + return priv; + +err_free: + urb_priv_free(ahcd, priv); +err: + return NULL; +} diff --git a/target/linux/adm5120/files/drivers/usb/host/adm5120-q.c b/target/linux/adm5120/files/drivers/usb/host/adm5120-q.c new file mode 100644 index 000000000..24542d273 --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-q.c @@ -0,0 +1,915 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * + * This file is licenced under the GPL. + */ + +#include <linux/irq.h> + +/*-------------------------------------------------------------------------*/ + +/* + * URB goes back to driver, and isn't reissued. + * It's completely gone from HC data structures. + * PRECONDITION: ahcd lock held, irqs blocked. + */ +static void +finish_urb(struct admhcd *ahcd, struct urb *urb) +__releases(ahcd->lock) +__acquires(ahcd->lock) +{ + urb_priv_free(ahcd, urb->hcpriv); + urb->hcpriv = NULL; + + spin_lock(&urb->lock); + if (likely(urb->status == -EINPROGRESS)) + urb->status = 0; + + /* report short control reads right even though the data TD always + * has TD_R set. (much simpler, but creates the 1-td limit.) + */ + if (unlikely(urb->transfer_flags & URB_SHORT_NOT_OK) + && unlikely(usb_pipecontrol(urb->pipe)) + && urb->actual_length < urb->transfer_buffer_length + && usb_pipein(urb->pipe) + && urb->status == 0) { + urb->status = -EREMOTEIO; +#ifdef ADMHC_VERBOSE_DEBUG + urb_print(urb, "SHORT", usb_pipeout (urb->pipe)); +#endif + } + spin_unlock(&urb->lock); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + admhcd_to_hcd(ahcd)->self.bandwidth_isoc_reqs--; + break; + case PIPE_INTERRUPT: + admhcd_to_hcd(ahcd)->self.bandwidth_int_reqs--; + break; + } + +#ifdef ADMHC_VERBOSE_DEBUG + urb_print(urb, "RET", usb_pipeout (urb->pipe)); +#endif + + /* urb->complete() can reenter this HCD */ + spin_unlock(&ahcd->lock); + usb_hcd_giveback_urb(admhcd_to_hcd(ahcd), urb); + spin_lock(&ahcd->lock); +} + + +/*-------------------------------------------------------------------------* + * ED handling functions + *-------------------------------------------------------------------------*/ + +#if 0 /* FIXME */ +/* search for the right schedule branch to use for a periodic ed. + * does some load balancing; returns the branch, or negative errno. + */ +static int balance(struct admhcd *ahcd, int interval, int load) +{ + int i, branch = -ENOSPC; + + /* iso periods can be huge; iso tds specify frame numbers */ + if (interval > NUM_INTS) + interval = NUM_INTS; + + /* search for the least loaded schedule branch of that period + * that has enough bandwidth left unreserved. + */ + for (i = 0; i < interval ; i++) { + if (branch < 0 || ahcd->load [branch] > ahcd->load [i]) { + int j; + + /* usb 1.1 says 90% of one frame */ + for (j = i; j < NUM_INTS; j += interval) { + if ((ahcd->load [j] + load) > 900) + break; + } + if (j < NUM_INTS) + continue; + branch = i; + } + } + return branch; +} +#endif + +/*-------------------------------------------------------------------------*/ + +#if 0 /* FIXME */ +/* both iso and interrupt requests have periods; this routine puts them + * into the schedule tree in the apppropriate place. most iso devices use + * 1msec periods, but that's not required. + */ +static void periodic_link (struct admhcd *ahcd, struct ed *ed) +{ + unsigned i; + + admhc_vdbg (ahcd, "link %sed %p branch %d [%dus.], interval %d\n", + (ed->hwINFO & cpu_to_hc32 (ahcd, ED_ISO)) ? "iso " : "", + ed, ed->branch, ed->load, ed->interval); + + for (i = ed->branch; i < NUM_INTS; i += ed->interval) { + struct ed **prev = &ahcd->periodic [i]; + __hc32 *prev_p = &ahcd->hcca->int_table [i]; + struct ed *here = *prev; + + /* sorting each branch by period (slow before fast) + * lets us share the faster parts of the tree. + * (plus maybe: put interrupt eds before iso) + */ + while (here && ed != here) { + if (ed->interval > here->interval) + break; + prev = &here->ed_next; + prev_p = &here->hwNextED; + here = *prev; + } + if (ed != here) { + ed->ed_next = here; + if (here) + ed->hwNextED = *prev_p; + wmb (); + *prev = ed; + *prev_p = cpu_to_hc32(ahcd, ed->dma); + wmb(); + } + ahcd->load [i] += ed->load; + } + admhcd_to_hcd(ahcd)->self.bandwidth_allocated += ed->load / ed->interval; +} +#endif + +/* link an ed into the HC chain */ + +static int ed_schedule(struct admhcd *ahcd, struct ed *ed) +{ + struct ed *old_tail; + + if (admhcd_to_hcd(ahcd)->state == HC_STATE_QUIESCING) + return -EAGAIN; + + ed->state = ED_OPER; + + old_tail = ahcd->ed_tails[ed->type]; + + ed->ed_next = old_tail->ed_next; + if (ed->ed_next) { + ed->ed_next->ed_prev = ed; + ed->hwNextED = cpu_to_hc32(ahcd, ed->ed_next->dma); + } + ed->ed_prev = old_tail; + + old_tail->ed_next = ed; + old_tail->hwNextED = cpu_to_hc32(ahcd, ed->dma); + + ahcd->ed_tails[ed->type] = ed; + + admhc_dma_enable(ahcd); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +#if 0 /* FIXME */ +/* scan the periodic table to find and unlink this ED */ +static void periodic_unlink (struct admhcd *ahcd, struct ed *ed) +{ + int i; + + for (i = ed->branch; i < NUM_INTS; i += ed->interval) { + struct ed *temp; + struct ed **prev = &ahcd->periodic [i]; + __hc32 *prev_p = &ahcd->hcca->int_table [i]; + + while (*prev && (temp = *prev) != ed) { + prev_p = &temp->hwNextED; + prev = &temp->ed_next; + } + if (*prev) { + *prev_p = ed->hwNextED; + *prev = ed->ed_next; + } + ahcd->load [i] -= ed->load; + } + + admhcd_to_hcd(ahcd)->self.bandwidth_allocated -= ed->load / ed->interval; + admhc_vdbg (ahcd, "unlink %sed %p branch %d [%dus.], interval %d\n", + (ed->hwINFO & cpu_to_hc32 (ahcd, ED_ISO)) ? "iso " : "", + ed, ed->branch, ed->load, ed->interval); +} +#endif + +/* unlink an ed from the HC chain. + * just the link to the ed is unlinked. + * the link from the ed still points to another operational ed or 0 + * so the HC can eventually finish the processing of the unlinked ed + * (assuming it already started that, which needn't be true). + * + * ED_UNLINK is a transient state: the HC may still see this ED, but soon + * it won't. ED_SKIP means the HC will finish its current transaction, + * but won't start anything new. The TD queue may still grow; device + * drivers don't know about this HCD-internal state. + * + * When the HC can't see the ED, something changes ED_UNLINK to one of: + * + * - ED_OPER: when there's any request queued, the ED gets rescheduled + * immediately. HC should be working on them. + * + * - ED_IDLE: when there's no TD queue. there's no reason for the HC + * to care about this ED; safe to disable the endpoint. + * + * When finish_unlinks() runs later, after SOF interrupt, it will often + * complete one or more URB unlinks before making that state change. + */ +static void ed_deschedule(struct admhcd *ahcd, struct ed *ed) +{ + ed->hwINFO |= cpu_to_hc32(ahcd, ED_SKIP); + wmb(); + ed->state = ED_UNLINK; + + /* remove this ED from the HC list */ + ed->ed_prev->hwNextED = ed->hwNextED; + + /* and remove it from our list also */ + ed->ed_prev->ed_next = ed->ed_next; + + if (ed->ed_next) + ed->ed_next->ed_prev = ed->ed_prev; + + if (ahcd->ed_tails[ed->type] == ed) + ahcd->ed_tails[ed->type] = ed->ed_prev; +} + +/*-------------------------------------------------------------------------*/ + +static struct ed *ed_create(struct admhcd *ahcd, unsigned int type, u32 info) +{ + struct ed *ed; + struct td *td; + + ed = ed_alloc(ahcd, GFP_ATOMIC); + if (!ed) + goto err; + + /* dummy td; end of td list for this ed */ + td = td_alloc(ahcd, GFP_ATOMIC); + if (!td) + goto err_free_ed; + + switch (type) { + case PIPE_INTERRUPT: + info |= ED_INT; + break; + case PIPE_ISOCHRONOUS: + info |= ED_ISO; + break; + } + + ed->dummy = td; + ed->state = ED_IDLE; + ed->type = type; + + ed->hwINFO = cpu_to_hc32(ahcd, info); + ed->hwTailP = cpu_to_hc32(ahcd, td->td_dma); + ed->hwHeadP = ed->hwTailP; /* ED_C, ED_H zeroed */ + + return ed; + +err_free_ed: + ed_free(ahcd, ed); +err: + return NULL; +} + +/* get and maybe (re)init an endpoint. init _should_ be done only as part + * of enumeration, usb_set_configuration() or usb_set_interface(). + */ +static struct ed *ed_get(struct admhcd *ahcd, struct usb_host_endpoint *ep, + struct usb_device *udev, unsigned int pipe, int interval) +{ + struct ed *ed; + unsigned long flags; + + spin_lock_irqsave(&ahcd->lock, flags); + ed = ep->hcpriv; + if (!ed) { + u32 info; + + /* FIXME: usbcore changes dev->devnum before SET_ADDRESS + * suceeds ... otherwise we wouldn't need "pipe". + */ + info = usb_pipedevice(pipe); + info |= (ep->desc.bEndpointAddress & ~USB_DIR_IN) << ED_EN_SHIFT; + info |= le16_to_cpu(ep->desc.wMaxPacketSize) << ED_MPS_SHIFT; + if (udev->speed == USB_SPEED_FULL) + info |= ED_SPEED_FULL; + + ed = ed_create(ahcd, usb_pipetype(pipe), info); + if (ed) + ep->hcpriv = ed; + } + spin_unlock_irqrestore(&ahcd->lock, flags); + + return ed; +} + +/*-------------------------------------------------------------------------*/ + +/* request unlinking of an endpoint from an operational HC. + * put the ep on the rm_list + * real work is done at the next start frame (SOFI) hardware interrupt + * caller guarantees HCD is running, so hardware access is safe, + * and that ed->state is ED_OPER + */ +static void start_ed_unlink(struct admhcd *ahcd, struct ed *ed) +{ + ed->hwINFO |= cpu_to_hc32 (ahcd, ED_DEQUEUE); + ed_deschedule(ahcd, ed); + + /* add this ED into the remove list */ + ed->ed_rm_next = ahcd->ed_rm_list; + ahcd->ed_rm_list = ed; + + /* enable SOF interrupt */ + admhc_intr_ack(ahcd, ADMHC_INTR_SOFI); + admhc_intr_enable(ahcd, ADMHC_INTR_SOFI); + /* flush those writes */ + admhc_writel_flush(ahcd); + + /* SOF interrupt might get delayed; record the frame counter value that + * indicates when the HC isn't looking at it, so concurrent unlinks + * behave. frame_no wraps every 2^16 msec, and changes right before + * SOF is triggered. + */ + ed->tick = admhc_frame_no(ahcd) + 1; +} + +/*-------------------------------------------------------------------------* + * TD handling functions + *-------------------------------------------------------------------------*/ + +/* enqueue next TD for this URB (OHCI spec 5.2.8.2) */ + +static void +td_fill(struct admhcd *ahcd, u32 info, dma_addr_t data, int len, + struct urb *urb, int index) +{ + struct td *td, *td_pt; + struct urb_priv *urb_priv = urb->hcpriv; + int hash; + u32 cbl = 0; + +#if 1 + if (index == (urb_priv->td_cnt - 1) && + ((urb->transfer_flags & URB_NO_INTERRUPT) == 0)) + cbl |= TD_IE; +#else + if (index == (urb_priv->td_cnt - 1)) + cbl |= TD_IE; +#endif + + /* use this td as the next dummy */ + td_pt = urb_priv->td[index]; + + /* fill the old dummy TD */ + td = urb_priv->td[index] = urb_priv->ed->dummy; + urb_priv->ed->dummy = td_pt; + + td->ed = urb_priv->ed; + td->next_dl_td = NULL; + td->index = index; + td->urb = urb; + td->data_dma = data; + if (!len) + data = 0; + + if (data) + cbl |= (len & TD_BL_MASK); + + info |= TD_OWN; + + /* setup hardware specific fields */ + td->hwINFO = cpu_to_hc32(ahcd, info); + td->hwDBP = cpu_to_hc32(ahcd, data); + td->hwCBL = cpu_to_hc32(ahcd, cbl); + td->hwNextTD = cpu_to_hc32(ahcd, td_pt->td_dma); + + /* append to queue */ + list_add_tail(&td->td_list, &td->ed->td_list); + + /* hash it for later reverse mapping */ + hash = TD_HASH_FUNC(td->td_dma); + td->td_hash = ahcd->td_hash[hash]; + ahcd->td_hash[hash] = td; + + /* HC might read the TD (or cachelines) right away ... */ + wmb(); + td->ed->hwTailP = td->hwNextTD; +} + +/*-------------------------------------------------------------------------*/ + +/* Prepare all TDs of a transfer, and queue them onto the ED. + * Caller guarantees HC is active. + * Usually the ED is already on the schedule, so TDs might be + * processed as soon as they're queued. + */ +static void td_submit_urb(struct admhcd *ahcd, struct urb *urb) +{ + struct urb_priv *urb_priv = urb->hcpriv; + dma_addr_t data; + int data_len = urb->transfer_buffer_length; + int cnt = 0; + u32 info = 0; + int is_out = usb_pipeout(urb->pipe); + int periodic = 0; + u32 toggle = 0; + struct td *td; + + /* OHCI handles the bulk/interrupt data toggles itself. We just + * use the device toggle bits for resetting, and rely on the fact + * that resetting toggle is meaningless if the endpoint is active. + */ + + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), is_out)) { + toggle = TD_T_CARRY; + } else { + toggle = TD_T_DATA0; + usb_settoggle(urb->dev, usb_pipeendpoint (urb->pipe), + is_out, 1); + } + + urb_priv->td_idx = 0; + list_add(&urb_priv->pending, &ahcd->pending); + + if (data_len) + data = urb->transfer_dma; + else + data = 0; + + /* NOTE: TD_CC is set so we can tell which TDs the HC processed by + * using TD_CC_GET, as well as by seeing them on the done list. + * (CC = NotAccessed ... 0x0F, or 0x0E in PSWs for ISO.) + */ + switch (urb_priv->ed->type) { + case PIPE_INTERRUPT: + info = is_out + ? TD_T_CARRY | TD_SCC_NOTACCESSED | TD_DP_OUT + : TD_T_CARRY | TD_SCC_NOTACCESSED | TD_DP_IN; + + /* setup service interval and starting frame number */ + info |= (urb->start_frame & TD_FN_MASK); + info |= (urb->interval & TD_ISI_MASK) << TD_ISI_SHIFT; + + td_fill(ahcd, info, data, data_len, urb, cnt); + cnt++; + + admhcd_to_hcd(ahcd)->self.bandwidth_int_reqs++; + break; + + case PIPE_BULK: + info = is_out + ? TD_SCC_NOTACCESSED | TD_DP_OUT + : TD_SCC_NOTACCESSED | TD_DP_IN; + + /* TDs _could_ transfer up to 8K each */ + while (data_len > TD_DATALEN_MAX) { + td_fill(ahcd, info | ((cnt) ? TD_T_CARRY : toggle), + data, TD_DATALEN_MAX, urb, cnt); + data += TD_DATALEN_MAX; + data_len -= TD_DATALEN_MAX; + cnt++; + } + + td_fill(ahcd, info | ((cnt) ? TD_T_CARRY : toggle), data, + data_len, urb, cnt); + cnt++; + + if ((urb->transfer_flags & URB_ZERO_PACKET) + && (cnt < urb_priv->td_cnt)) { + td_fill(ahcd, info | ((cnt) ? TD_T_CARRY : toggle), + 0, 0, urb, cnt); + cnt++; + } + break; + + /* control manages DATA0/DATA1 toggle per-request; SETUP resets it, + * any DATA phase works normally, and the STATUS ack is special. + */ + case PIPE_CONTROL: + /* fill a TD for the setup */ + info = TD_SCC_NOTACCESSED | TD_DP_SETUP | TD_T_DATA0; + td_fill(ahcd, info, urb->setup_dma, 8, urb, cnt++); + + if (data_len > 0) { + /* fill a TD for the data */ + info = TD_SCC_NOTACCESSED | TD_T_DATA1; + info |= is_out ? TD_DP_OUT : TD_DP_IN; + /* NOTE: mishandles transfers >8K, some >4K */ + td_fill(ahcd, info, data, data_len, urb, cnt++); + } + + /* fill a TD for the ACK */ + info = (is_out || data_len == 0) + ? TD_SCC_NOTACCESSED | TD_DP_IN | TD_T_DATA1 + : TD_SCC_NOTACCESSED | TD_DP_OUT | TD_T_DATA1; + td_fill(ahcd, info, data, 0, urb, cnt++); + + break; + + /* ISO has no retransmit, so no toggle; + * Each TD could handle multiple consecutive frames (interval 1); + * we could often reduce the number of TDs here. + */ + case PIPE_ISOCHRONOUS: + info = TD_SCC_NOTACCESSED; + for (cnt = 0; cnt < urb->number_of_packets; cnt++) { + int frame = urb->start_frame; + + frame += cnt * urb->interval; + frame &= TD_FN_MASK; + td_fill(ahcd, info | frame, + data + urb->iso_frame_desc[cnt].offset, + urb->iso_frame_desc[cnt].length, urb, cnt); + } + admhcd_to_hcd(ahcd)->self.bandwidth_isoc_reqs++; + break; + } + + if (urb_priv->td_cnt != cnt) + admhc_err(ahcd, "bad number of tds created for urb %p\n", urb); +} + +/*-------------------------------------------------------------------------* + * Done List handling functions + *-------------------------------------------------------------------------*/ + +/* calculate transfer length/status and update the urb + * PRECONDITION: irqsafe (only for urb->status locking) + */ +static void td_done(struct admhcd *ahcd, struct urb *urb, struct td *td) +{ + u32 info = hc32_to_cpup(ahcd, &td->hwINFO); + int type = usb_pipetype(urb->pipe); + int cc = TD_CC_NOERROR; + + /* ISO ... drivers see per-TD length/status */ + if (type == PIPE_ISOCHRONOUS) { +#if 0 + /* TODO */ + int dlen = 0; + + /* NOTE: assumes FC in tdINFO == 0, and that + * only the first of 0..MAXPSW psws is used. + */ + + cc = TD_CC_GET(td); + if (tdINFO & TD_CC) /* hc didn't touch? */ + return; + + if (usb_pipeout (urb->pipe)) + dlen = urb->iso_frame_desc [td->index].length; + else { + /* short reads are always OK for ISO */ + if (cc == TD_DATAUNDERRUN) + cc = TD_CC_NOERROR; + dlen = tdPSW & 0x3ff; + } + urb->actual_length += dlen; + urb->iso_frame_desc [td->index].actual_length = dlen; + urb->iso_frame_desc [td->index].status = cc_to_error [cc]; + + if (cc != TD_CC_NOERROR) + admhc_vdbg (ahcd, + "urb %p iso td %p (%d) len %d cc %d\n", + urb, td, 1 + td->index, dlen, cc); +#endif + /* BULK, INT, CONTROL ... drivers see aggregate length/status, + * except that "setup" bytes aren't counted and "short" transfers + * might not be reported as errors. + */ + } else { + u32 bl = TD_BL_GET(hc32_to_cpup(ahcd, &td->hwCBL)); + u32 tdDBP = hc32_to_cpup(ahcd, &td->hwDBP); + + cc = TD_CC_GET(info); + + /* update packet status if needed (short is normally ok) */ + if (cc == TD_CC_DATAUNDERRUN + && !(urb->transfer_flags & URB_SHORT_NOT_OK)) + cc = TD_CC_NOERROR; + + if (cc != TD_CC_NOERROR && cc < TD_CC_HCD0) { + admhc_dump_ed(ahcd, "CC ERROR", td->ed, 1); + spin_lock(&urb->lock); + if (urb->status == -EINPROGRESS) + urb->status = cc_to_error[cc]; + spin_unlock(&urb->lock); + } + + /* count all non-empty packets except control SETUP packet */ + if ((type != PIPE_CONTROL || td->index != 0) && tdDBP != 0) { + urb->actual_length += tdDBP - td->data_dma + bl; + } + + if (cc != TD_CC_NOERROR && cc < TD_CC_HCD0) + admhc_vdbg(ahcd, + "urb %p td %p (%d) cc %d, len=%d/%d\n", + urb, td, td->index, cc, + urb->actual_length, + urb->transfer_buffer_length); + } + + list_del(&td->td_list); + +} + +/*-------------------------------------------------------------------------*/ + +static inline struct td * +ed_halted(struct admhcd *ahcd, struct td *td, int cc, struct td *rev) +{ + struct urb *urb = td->urb; + struct ed *ed = td->ed; + struct list_head *tmp = td->td_list.next; + __hc32 toggle = ed->hwHeadP & cpu_to_hc32 (ahcd, ED_C); + + admhc_dump_ed(ahcd, "ed halted", td->ed, 1); + /* clear ed halt; this is the td that caused it, but keep it inactive + * until its urb->complete() has a chance to clean up. + */ + ed->hwINFO |= cpu_to_hc32 (ahcd, ED_SKIP); + wmb (); + ed->hwHeadP &= ~cpu_to_hc32 (ahcd, ED_H); + + /* put any later tds from this urb onto the donelist, after 'td', + * order won't matter here: no errors, and nothing was transferred. + * also patch the ed so it looks as if those tds completed normally. + */ + while (tmp != &ed->td_list) { + struct td *next; + __hc32 info; + + next = list_entry(tmp, struct td, td_list); + tmp = next->td_list.next; + + if (next->urb != urb) + break; + + /* NOTE: if multi-td control DATA segments get supported, + * this urb had one of them, this td wasn't the last td + * in that segment (TD_R clear), this ed halted because + * of a short read, _and_ URB_SHORT_NOT_OK is clear ... + * then we need to leave the control STATUS packet queued + * and clear ED_SKIP. + */ + info = next->hwINFO; +#if 0 /* FIXME */ + info |= cpu_to_hc32 (ahcd, TD_DONE); + info &= ~cpu_to_hc32 (ahcd, TD_CC); +#endif + next->hwINFO = info; + + next->next_dl_td = rev; + rev = next; + + ed->hwHeadP = next->hwNextTD | toggle; + } + + /* help for troubleshooting: report anything that + * looks odd ... that doesn't include protocol stalls + * (or maybe some other things) + */ + switch (cc) { + case TD_CC_DATAUNDERRUN: + if ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0) + break; + /* fallthrough */ + case TD_CC_STALL: + if (usb_pipecontrol (urb->pipe)) + break; + /* fallthrough */ + default: + admhc_dbg (ahcd, + "urb %p path %s ep%d%s %08x cc %d --> status %d\n", + urb, urb->dev->devpath, + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + hc32_to_cpu(ahcd, td->hwINFO), + cc, cc_to_error [cc]); + } + + return rev; +} + +/*-------------------------------------------------------------------------*/ + +/* there are some urbs/eds to unlink; called in_irq(), with HCD locked */ +static void +finish_unlinks(struct admhcd *ahcd, u16 tick) +{ + struct ed *ed, **last; + +rescan_all: + for (last = &ahcd->ed_rm_list, ed = *last; ed != NULL; ed = *last) { + struct list_head *entry, *tmp; + int completed, modified; + __hc32 *prev; + + /* only take off EDs that the HC isn't using, accounting for + * frame counter wraps and EDs with partially retired TDs + */ + if (likely(HC_IS_RUNNING(admhcd_to_hcd(ahcd)->state))) { + if (tick_before (tick, ed->tick)) { +skip_ed: + last = &ed->ed_rm_next; + continue; + } + + if (!list_empty (&ed->td_list)) { + struct td *td; + u32 head; + + td = list_entry(ed->td_list.next, struct td, + td_list); + head = hc32_to_cpu(ahcd, ed->hwHeadP) & + TD_MASK; + + /* INTR_WDH may need to clean up first */ + if (td->td_dma != head) + goto skip_ed; + } + } + + /* reentrancy: if we drop the schedule lock, someone might + * have modified this list. normally it's just prepending + * entries (which we'd ignore), but paranoia won't hurt. + */ + *last = ed->ed_rm_next; + ed->ed_rm_next = NULL; + modified = 0; + + /* unlink urbs as requested, but rescan the list after + * we call a completion since it might have unlinked + * another (earlier) urb + * + * When we get here, the HC doesn't see this ed. But it + * must not be rescheduled until all completed URBs have + * been given back to the driver. + */ +rescan_this: + completed = 0; + prev = &ed->hwHeadP; + list_for_each_safe (entry, tmp, &ed->td_list) { + struct td *td; + struct urb *urb; + struct urb_priv *urb_priv; + __hc32 savebits; + + td = list_entry(entry, struct td, td_list); + urb = td->urb; + urb_priv = td->urb->hcpriv; + + if (urb->status == -EINPROGRESS) { + prev = &td->hwNextTD; + continue; + } + + if ((urb_priv) == NULL) + continue; + + /* patch pointer hc uses */ + savebits = *prev & ~cpu_to_hc32(ahcd, TD_MASK); + *prev = td->hwNextTD | savebits; + + /* HC may have partly processed this TD */ + urb_print(urb, "PARTIAL",1); + td_done(ahcd, urb, td); + urb_priv->td_idx++; + + /* if URB is done, clean up */ + if (urb_priv->td_idx == urb_priv->td_cnt) { + modified = completed = 1; + finish_urb(ahcd, urb); + } + } + if (completed && !list_empty (&ed->td_list)) + goto rescan_this; + + /* ED's now officially unlinked, hc doesn't see */ + ed->state = ED_IDLE; + ed->hwHeadP &= ~cpu_to_hc32(ahcd, ED_H); + ed->hwNextED = 0; + wmb (); + ed->hwINFO &= ~cpu_to_hc32 (ahcd, ED_SKIP | ED_DEQUEUE); + + /* but if there's work queued, reschedule */ + if (!list_empty (&ed->td_list)) { + if (HC_IS_RUNNING(admhcd_to_hcd(ahcd)->state)) + ed_schedule(ahcd, ed); + } + + if (modified) + goto rescan_all; + } +} + +/*-------------------------------------------------------------------------*/ + +/* + * Process normal completions (error or success) and clean the schedules. + * + * This is the main path for handing urbs back to drivers. The only other + * path is finish_unlinks(), which unlinks URBs using ed_rm_list, instead of + * scanning the (re-reversed) donelist as this does. + */ + +static void ed_update(struct admhcd *ahcd, struct ed *ed) +{ + struct list_head *entry,*tmp; + + admhc_dump_ed(ahcd, "ed update", ed, 1); + + list_for_each_safe(entry, tmp, &ed->td_list) { + struct td *td = list_entry(entry, struct td, td_list); + struct urb *urb = td->urb; + struct urb_priv *urb_priv = urb->hcpriv; + + if (hc32_to_cpup(ahcd, &td->hwINFO) & TD_OWN) + break; + + /* update URB's length and status from TD */ + td_done(ahcd, urb, td); + urb_priv->td_idx++; + + /* If all this urb's TDs are done, call complete() */ + if (urb_priv->td_idx == urb_priv->td_cnt) + finish_urb(ahcd, urb); + + /* clean schedule: unlink EDs that are no longer busy */ + if (list_empty(&ed->td_list)) { + if (ed->state == ED_OPER) + start_ed_unlink(ahcd, ed); + + /* ... reenabling halted EDs only after fault cleanup */ + } else if ((ed->hwINFO & cpu_to_hc32 (ahcd, + ED_SKIP | ED_DEQUEUE)) + == cpu_to_hc32 (ahcd, ED_SKIP)) { + td = list_entry(ed->td_list.next, struct td, td_list); +#if 0 + if (!(td->hwINFO & cpu_to_hc32 (ahcd, TD_DONE))) { + ed->hwINFO &= ~cpu_to_hc32 (ahcd, ED_SKIP); + /* ... hc may need waking-up */ + switch (ed->type) { + case PIPE_CONTROL: + admhc_writel (ahcd, OHCI_CLF, + &ahcd->regs->cmdstatus); + break; + case PIPE_BULK: + admhc_writel (ahcd, OHCI_BLF, + &ahcd->regs->cmdstatus); + break; + } + } +#else + if ((td->hwINFO & cpu_to_hc32(ahcd, TD_OWN))) + ed->hwINFO &= ~cpu_to_hc32(ahcd, ED_SKIP); +#endif + } + + } +} + +static void ed_halt(struct admhcd *ahcd, struct ed *ed) +{ + admhc_dump_ed(ahcd, "ed_halt", ed, 1); +} + +/* there are some tds completed; called in_irq(), with HCD locked */ +static void admhc_td_complete(struct admhcd *ahcd) +{ + struct ed *ed; + + for (ed = ahcd->ed_head; ed; ed = ed->ed_next) { + if (ed->state != ED_OPER) + continue; + + if (hc32_to_cpup(ahcd, &ed->hwINFO) & ED_SKIP) + continue; + + if (hc32_to_cpup(ahcd, &ed->hwHeadP) & ED_H) { + ed_halt(ahcd, ed); + continue; + } + + ed_update(ahcd, ed); + } +} diff --git a/target/linux/adm5120/files/drivers/usb/host/adm5120.h b/target/linux/adm5120/files/drivers/usb/host/adm5120.h new file mode 100644 index 000000000..95616f27a --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120.h @@ -0,0 +1,714 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * + * This file is licenced under the GPL. + */ + +/* + * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to + * __leXX (normally) or __beXX (given OHCI_BIG_ENDIAN), depending on the + * host controller implementation. + */ +typedef __u32 __bitwise __hc32; +typedef __u16 __bitwise __hc16; + +/* + * OHCI Endpoint Descriptor (ED) ... holds TD queue + * See OHCI spec, section 4.2 + * + * This is a "Queue Head" for those transfers, which is why + * both EHCI and UHCI call similar structures a "QH". + */ + +#define TD_DATALEN_MAX 4096 + +#define ED_ALIGN 16 +#define ED_MASK ((u32)~(ED_ALIGN-1)) /* strip hw status in low addr bits */ + +struct ed { + /* first fields are hardware-specified */ + __hc32 hwINFO; /* endpoint config bitmap */ + /* info bits defined by hcd */ +#define ED_DEQUEUE (1 << 27) + /* info bits defined by the hardware */ +#define ED_MPS_SHIFT 16 +#define ED_MPS_MASK ((1 << 11)-1) +#define ED_MPS_GET(x) (((x) >> ED_MPS_SHIFT) & ED_MPS_MASK) +#define ED_ISO (1 << 15) /* isochronous endpoint */ +#define ED_SKIP (1 << 14) +#define ED_SPEED_FULL (1 << 13) /* fullspeed device */ +#define ED_INT (1 << 11) /* interrupt endpoint */ +#define ED_EN_SHIFT 7 /* endpoint shift */ +#define ED_EN_MASK ((1 << 4)-1) /* endpoint mask */ +#define ED_EN_GET(x) (((x) >> ED_EN_SHIFT) & ED_EN_MASK) +#define ED_FA_MASK ((1 << 7)-1) /* function address mask */ +#define ED_FA_GET(x) ((x) & ED_FA_MASK) + __hc32 hwTailP; /* tail of TD list */ + __hc32 hwHeadP; /* head of TD list (hc r/w) */ +#define ED_C (0x02) /* toggle carry */ +#define ED_H (0x01) /* halted */ + __hc32 hwNextED; /* next ED in list */ + + /* rest are purely for the driver's use */ + dma_addr_t dma; /* addr of ED */ + struct td *dummy; /* next TD to activate */ + + struct list_head urb_list; /* list of our URBs */ + + struct list_head ed_list; /* list of all EDs*/ + struct list_head rm_list; /* for remove list */ + + /* host's view of schedule */ + struct ed *ed_next; /* on schedule list */ + struct ed *ed_prev; /* for non-interrupt EDs */ + struct ed *ed_rm_next; /* on rm list */ + struct ed *ed_soft_list; /* on software int list */ + struct list_head td_list; /* "shadow list" of our TDs */ + + /* create --> IDLE --> OPER --> ... --> IDLE --> destroy + * usually: OPER --> UNLINK --> (IDLE | OPER) --> ... + */ + u8 state; /* ED_{IDLE,UNLINK,OPER} */ +#define ED_IDLE 0x00 /* NOT linked to HC */ +#define ED_UNLINK 0x01 /* being unlinked from hc */ +#define ED_OPER 0x02 /* IS linked to hc */ + + u8 type; /* PIPE_{BULK,...} */ + + /* periodic scheduling params (for intr and iso) */ + u8 branch; + u16 interval; + u16 load; + u16 last_iso; /* iso only */ + + /* HC may see EDs on rm_list until next frame (frame_no == tick) */ + u16 tick; +} __attribute__ ((aligned(ED_ALIGN))); + +/* + * OHCI Transfer Descriptor (TD) ... one per transfer segment + * See OHCI spec, sections 4.3.1 (general = control/bulk/interrupt) + * and 4.3.2 (iso) + */ + +#define TD_ALIGN 32 +#define TD_MASK ((u32)~(TD_ALIGN-1)) /* strip hw status in low addr bits */ + +struct td { + /* first fields are hardware-specified */ + __hc32 hwINFO; /* transfer info bitmask */ + + /* hwINFO bits */ +#define TD_OWN (1 << 31) /* owner of the descriptor */ +#define TD_CC_SHIFT 27 /* condition code */ +#define TD_CC_MASK 0xf +#define TD_CC (TD_CC_MASK << TD_CC_SHIFT) +#define TD_CC_GET(x) (((x) >> TD_CC_SHIFT) & TD_CC_MASK) + +#define TD_EC_SHIFT 25 /* error count */ +#define TD_EC_MASK 0x3 +#define TD_EC (TD_EC_MASK << TD_EC_SHIFT) +#define TD_EC_GET(x) ((x >> TD_EC_SHIFT) & TD_EC_MASK) +#define TD_T_SHIFT 23 /* data toggle state */ +#define TD_T_MASK 0x3 +#define TD_T (TD_T_MASK << TD_T_SHIFT) +#define TD_T_DATA0 (0x2 << TD_T_SHIFT) /* DATA0 */ +#define TD_T_DATA1 (0x3 << TD_T_SHIFT) /* DATA1 */ +#define TD_T_CARRY (0x0 << TD_T_SHIFT) /* uses ED_C */ +#define TD_T_GET(x) (((x) >> TD_T_SHIFT) & TD_T_MASK) +#define TD_DP_SHIFT 21 /* direction/pid */ +#define TD_DP_MASK 0x3 +#define TD_DP (TD_DP_MASK << TD_DP_SHIFT) +#define TD_DP_SETUP (0x0 << TD_DP_SHIFT) /* SETUP pid */ +#define TD_DP_OUT (0x1 << TD_DP_SHIFT) /* OUT pid */ +#define TD_DP_IN (0x2 << TD_DP_SHIFT) /* IN pid */ +#define TD_ISI_SHIFT 8 /* Interrupt Service Interval */ +#define TD_ISI_MASK 0x3f +#define TD_ISI_GET(x) (((x) >> TD_ISI_SHIFT) & TD_ISI_MASK) +#define TD_FN_MASK 0x3f /* frame number */ +#define TD_FN_GET(x) ((x) & TD_FN_MASK) + + __hc32 hwDBP; /* Data Buffer Pointer (or 0) */ + __hc32 hwCBL; /* Controller/Buffer Length */ + + /* hwCBL bits */ +#define TD_BL_MASK 0xffff /* buffer length */ +#define TD_BL_GET(x) ((x) & TD_BL_MASK) +#define TD_IE (1 << 16) /* interrupt enable */ + __hc32 hwNextTD; /* Next TD Pointer */ + + /* rest are purely for the driver's use */ + __u8 index; + struct ed *ed; + struct td *td_hash; /* dma-->td hashtable */ + struct td *next_dl_td; + struct urb *urb; + + dma_addr_t td_dma; /* addr of this TD */ + dma_addr_t data_dma; /* addr of data it points to */ + + struct list_head td_list; /* "shadow list", TDs on same ED */ + + u32 flags; +#define TD_FLAG_DONE (1 << 17) /* retired to done list */ +#define TD_FLAG_ISO (1 << 16) /* copy of ED_ISO */ +} __attribute__ ((aligned(TD_ALIGN))); /* c/b/i need 16; only iso needs 32 */ + +/* + * Hardware transfer status codes -- CC from td->hwINFO + */ +#define TD_CC_NOERROR 0x00 +#define TD_CC_CRC 0x01 +#define TD_CC_BITSTUFFING 0x02 +#define TD_CC_DATATOGGLEM 0x03 +#define TD_CC_STALL 0x04 +#define TD_CC_DEVNOTRESP 0x05 +#define TD_CC_PIDCHECKFAIL 0x06 +#define TD_CC_UNEXPECTEDPID 0x07 +#define TD_CC_DATAOVERRUN 0x08 +#define TD_CC_DATAUNDERRUN 0x09 + /* 0x0A, 0x0B reserved for hardware */ +#define TD_CC_BUFFEROVERRUN 0x0C +#define TD_CC_BUFFERUNDERRUN 0x0D + /* 0x0E, 0x0F reserved for HCD */ +#define TD_CC_HCD0 0x0E +#define TD_CC_NOTACCESSED 0x0F + +/* + * preshifted status codes + */ +#define TD_SCC_NOTACCESSED (TD_CC_NOTACCESSED << TD_CC_SHIFT) + + +/* map OHCI TD status codes (CC) to errno values */ +static const int cc_to_error [16] = { + /* No Error */ 0, + /* CRC Error */ -EILSEQ, + /* Bit Stuff */ -EPROTO, + /* Data Togg */ -EILSEQ, + /* Stall */ -EPIPE, + /* DevNotResp */ -ETIME, + /* PIDCheck */ -EPROTO, + /* UnExpPID */ -EPROTO, + /* DataOver */ -EOVERFLOW, + /* DataUnder */ -EREMOTEIO, + /* (for hw) */ -EIO, + /* (for hw) */ -EIO, + /* BufferOver */ -ECOMM, + /* BuffUnder */ -ENOSR, + /* (for HCD) */ -EALREADY, + /* (for HCD) */ -EALREADY +}; + +#define NUM_INTS 32 + +/* + * This is the structure of the OHCI controller's memory mapped I/O region. + * You must use readl() and writel() (in <asm/io.h>) to access these fields!! + * Layout is in section 7 (and appendix B) of the spec. + */ +struct admhcd_regs { + __hc32 gencontrol; /* General Control */ + __hc32 int_status; /* Interrupt Status */ + __hc32 int_enable; /* Interrupt Enable */ + __hc32 reserved00; + __hc32 host_control; /* Host General Control */ + __hc32 reserved01; + __hc32 fminterval; /* Frame Interval */ + __hc32 fmnumber; /* Frame Number */ + __hc32 reserved02; + __hc32 reserved03; + __hc32 reserved04; + __hc32 reserved05; + __hc32 reserved06; + __hc32 reserved07; + __hc32 reserved08; + __hc32 reserved09; + __hc32 reserved10; + __hc32 reserved11; + __hc32 reserved12; + __hc32 reserved13; + __hc32 reserved14; + __hc32 reserved15; + __hc32 reserved16; + __hc32 reserved17; + __hc32 reserved18; + __hc32 reserved19; + __hc32 reserved20; + __hc32 reserved21; + __hc32 lsthresh; /* Low Speed Threshold */ + __hc32 rhdesc; /* Root Hub Descriptor */ +#define MAX_ROOT_PORTS 2 + __hc32 portstatus[MAX_ROOT_PORTS]; /* Port Status */ + __hc32 hosthead; /* Host Descriptor Head */ +} __attribute__ ((aligned(32))); + +/* + * General Control register bits + */ +#define ADMHC_CTRL_UHFE (1 << 0) /* USB Host Function Enable */ +#define ADMHC_CTRL_SIR (1 << 1) /* Software Interrupt request */ +#define ADMHC_CTRL_DMAA (1 << 2) /* DMA Arbitration Control */ +#define ADMHC_CTRL_SR (1 << 3) /* Software Reset */ + +/* + * Host General Control register bits + */ +#define ADMHC_HC_BUSS 0x3 /* USB bus state */ +#define ADMHC_BUSS_RESET 0x0 +#define ADMHC_BUSS_RESUME 0x1 +#define ADMHC_BUSS_OPER 0x2 +#define ADMHC_BUSS_SUSPEND 0x3 +#define ADMHC_HC_DMAE (1 << 2) /* DMA enable */ + +/* + * Interrupt Status/Enable register bits + */ +#define ADMHC_INTR_SOFI (1 << 4) /* start of frame */ +#define ADMHC_INTR_RESI (1 << 5) /* resume detected */ +#define ADMHC_INTR_BABI (1 << 8) /* babble detected */ +#define ADMHC_INTR_INSM (1 << 9) /* root hub status change */ +#define ADMHC_INTR_SO (1 << 10) /* scheduling overrun */ +#define ADMHC_INTR_FNO (1 << 11) /* frame number overflow */ +#define ADMHC_INTR_TDC (1 << 20) /* transfer descriptor completed */ +#define ADMHC_INTR_SWI (1 << 29) /* software interrupt */ +#define ADMHC_INTR_FATI (1 << 30) /* fatal error */ +#define ADMHC_INTR_INTA (1 << 31) /* interrupt active */ + +#define ADMHC_INTR_MIE (1 << 31) /* master interrupt enable */ + +/* + * SOF Frame Interval register bits + */ +#define ADMHC_SFI_FI_MASK ((1 << 14)-1) /* Frame Interval value */ +#define ADMHC_SFI_FSLDP_SHIFT 16 +#define ADMHC_SFI_FSLDP_MASK ((1 << 15)-1) +#define ADMHC_SFI_FIT (1 << 31) /* Frame Interval Toggle */ + +/* + * SOF Frame Number register bits + */ +#define ADMHC_SFN_FN_MASK ((1 << 16)-1) /* Frame Number Mask */ +#define ADMHC_SFN_FR_SHIFT 16 /* Frame Remaining Shift */ +#define ADMHC_SFN_FR_MASK ((1 << 14)-1) /* Frame Remaining Mask */ +#define ADMHC_SFN_FRT (1 << 31) /* Frame Remaining Toggle */ + +/* + * Root Hub Descriptor register bits + */ +#define ADMHC_RH_NUMP 0xff /* number of ports */ +#define ADMHC_RH_PSM (1 << 8) /* power switching mode */ +#define ADMHC_RH_NPS (1 << 9) /* no power switching */ +#define ADMHC_RH_OCPM (1 << 10) /* over current protection mode */ +#define ADMHC_RH_NOCP (1 << 11) /* no over current protection */ +#define ADMHC_RH_PPCM (0xff << 16) /* port power control */ + +#define ADMHC_RH_LPS (1 << 24) /* local power switch */ +#define ADMHC_RH_OCI (1 << 25) /* over current indicator */ + +/* status change bits */ +#define ADMHC_RH_LPSC (1 << 26) /* local power switch change */ +#define ADMHC_RH_OCIC (1 << 27) /* over current indicator change */ + +#define ADMHC_RH_DRWE (1 << 28) /* device remote wakeup enable */ +#define ADMHC_RH_CRWE (1 << 29) /* clear remote wakeup enable */ + +#define ADMHC_RH_CGP (1 << 24) /* clear global power */ +#define ADMHC_RH_SGP (1 << 26) /* set global power */ + +/* + * Port Status register bits + */ +#define ADMHC_PS_CCS (1 << 0) /* current connect status */ +#define ADMHC_PS_PES (1 << 1) /* port enable status */ +#define ADMHC_PS_PSS (1 << 2) /* port suspend status */ +#define ADMHC_PS_POCI (1 << 3) /* port over current indicator */ +#define ADMHC_PS_PRS (1 << 4) /* port reset status */ +#define ADMHC_PS_PPS (1 << 8) /* port power status */ +#define ADMHC_PS_LSDA (1 << 9) /* low speed device attached */ + +/* status change bits */ +#define ADMHC_PS_CSC (1 << 16) /* connect status change */ +#define ADMHC_PS_PESC (1 << 17) /* port enable status change */ +#define ADMHC_PS_PSSC (1 << 18) /* port suspend status change */ +#define ADMHC_PS_OCIC (1 << 19) /* over current indicator change */ +#define ADMHC_PS_PRSC (1 << 20) /* port reset status change */ + +/* port feature bits */ +#define ADMHC_PS_CPE (1 << 0) /* clear port enable */ +#define ADMHC_PS_SPE (1 << 1) /* set port enable */ +#define ADMHC_PS_SPS (1 << 2) /* set port suspend */ +#define ADMHC_PS_CPS (1 << 3) /* clear suspend status */ +#define ADMHC_PS_SPR (1 << 4) /* set port reset */ +#define ADMHC_PS_SPP (1 << 8) /* set port power */ +#define ADMHC_PS_CPP (1 << 9) /* clear port power */ + +/* + * the POTPGT value is not defined in the ADMHC, so define a dummy value + */ +#define ADMHC_POTPGT 2 /* in ms */ + +/* hcd-private per-urb state */ +struct urb_priv { + struct ed *ed; + struct list_head pending; /* URBs on the same ED */ + + u32 td_cnt; /* # tds in this request */ + u32 td_idx; /* index of the current td */ + struct td *td[0]; /* all TDs in this request */ +}; + +#define TD_HASH_SIZE 64 /* power'o'two */ +/* sizeof (struct td) ~= 64 == 2^6 ... */ +#define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 6)) % TD_HASH_SIZE) + +/* + * This is the full ADMHCD controller description + * + * Note how the "proper" USB information is just + * a subset of what the full implementation needs. (Linus) + */ + +struct admhcd { + spinlock_t lock; + + /* + * I/O memory used to communicate with the HC (dma-consistent) + */ + struct admhcd_regs __iomem *regs; + + /* + * hcd adds to schedule for a live hc any time, but removals finish + * only at the start of the next frame. + */ + + struct ed *ed_head; + struct ed *ed_tails[4]; + + struct ed *ed_rm_list; /* to be removed */ + struct ed *ed_halt_list; /* halted due to an error */ + struct ed *ed_soft_list; /* for software interrupt processing */ + + struct ed *periodic[NUM_INTS]; /* shadow int_table */ + +#if 0 /* TODO: remove? */ + /* + * OTG controllers and transceivers need software interaction; + * other external transceivers should be software-transparent + */ + struct otg_transceiver *transceiver; +#endif + + /* + * memory management for queue data structures + */ + struct dma_pool *td_cache; + struct dma_pool *ed_cache; + struct td *td_hash[TD_HASH_SIZE]; + struct list_head pending; + + /* + * driver state + */ + int num_ports; + int load[NUM_INTS]; + u32 host_control; /* copy of the host_control reg */ + unsigned long next_statechange; /* suspend/resume */ + u32 fminterval; /* saved register */ + unsigned autostop:1; /* rh auto stopping/stopped */ + + unsigned long flags; /* for HC bugs */ +#define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */ +#define OHCI_QUIRK_SUPERIO 0x02 /* natsemi */ +#define OHCI_QUIRK_INITRESET 0x04 /* SiS, OPTi, ... */ +#define OHCI_QUIRK_BE_DESC 0x08 /* BE descriptors */ +#define OHCI_QUIRK_BE_MMIO 0x10 /* BE registers */ +#define OHCI_QUIRK_ZFMICRO 0x20 /* Compaq ZFMicro chipset*/ + // there are also chip quirks/bugs in init logic +}; + +/* convert between an hcd pointer and the corresponding ahcd_hcd */ +static inline struct admhcd *hcd_to_admhcd(struct usb_hcd *hcd) +{ + return (struct admhcd *)(hcd->hcd_priv); +} +static inline struct usb_hcd *admhcd_to_hcd(const struct admhcd *ahcd) +{ + return container_of((void *)ahcd, struct usb_hcd, hcd_priv); +} + +/*-------------------------------------------------------------------------*/ + +#ifndef DEBUG +#define STUB_DEBUG_FILES +#endif /* DEBUG */ + +#define admhc_dbg(ahcd, fmt, args...) \ + dev_dbg(admhcd_to_hcd(ahcd)->self.controller , fmt , ## args ) +#define admhc_err(ahcd, fmt, args...) \ + dev_err(admhcd_to_hcd(ahcd)->self.controller , fmt , ## args ) +#define ahcd_info(ahcd, fmt, args...) \ + dev_info(admhcd_to_hcd(ahcd)->self.controller , fmt , ## args ) +#define admhc_warn(ahcd, fmt, args...) \ + dev_warn(admhcd_to_hcd(ahcd)->self.controller , fmt , ## args ) + +#ifdef ADMHC_VERBOSE_DEBUG +# define admhc_vdbg admhc_dbg +#else +# define admhc_vdbg(ahcd, fmt, args...) do { } while (0) +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * While most USB host controllers implement their registers and + * in-memory communication descriptors in little-endian format, + * a minority (notably the IBM STB04XXX and the Motorola MPC5200 + * processors) implement them in big endian format. + * + * In addition some more exotic implementations like the Toshiba + * Spider (aka SCC) cell southbridge are "mixed" endian, that is, + * they have a different endianness for registers vs. in-memory + * descriptors. + * + * This attempts to support either format at compile time without a + * runtime penalty, or both formats with the additional overhead + * of checking a flag bit. + * + * That leads to some tricky Kconfig rules howevber. There are + * different defaults based on some arch/ppc platforms, though + * the basic rules are: + * + * Controller type Kconfig options needed + * --------------- ---------------------- + * little endian CONFIG_USB_ADMHC_LITTLE_ENDIAN + * + * fully big endian CONFIG_USB_ADMHC_BIG_ENDIAN_DESC _and_ + * CONFIG_USB_ADMHC_BIG_ENDIAN_MMIO + * + * mixed endian CONFIG_USB_ADMHC_LITTLE_ENDIAN _and_ + * CONFIG_USB_OHCI_BIG_ENDIAN_{MMIO,DESC} + * + * (If you have a mixed endian controller, you -must- also define + * CONFIG_USB_ADMHC_LITTLE_ENDIAN or things will not work when building + * both your mixed endian and a fully big endian controller support in + * the same kernel image). + */ + +#ifdef CONFIG_USB_ADMHC_BIG_ENDIAN_DESC +#ifdef CONFIG_USB_ADMHC_LITTLE_ENDIAN +#define big_endian_desc(ahcd) (ahcd->flags & OHCI_QUIRK_BE_DESC) +#else +#define big_endian_desc(ahcd) 1 /* only big endian */ +#endif +#else +#define big_endian_desc(ahcd) 0 /* only little endian */ +#endif + +#ifdef CONFIG_USB_ADMHC_BIG_ENDIAN_MMIO +#ifdef CONFIG_USB_ADMHC_LITTLE_ENDIAN +#define big_endian_mmio(ahcd) (ahcd->flags & OHCI_QUIRK_BE_MMIO) +#else +#define big_endian_mmio(ahcd) 1 /* only big endian */ +#endif +#else +#define big_endian_mmio(ahcd) 0 /* only little endian */ +#endif + +/* + * Big-endian read/write functions are arch-specific. + * Other arches can be added if/when they're needed. + * + * REVISIT: arch/powerpc now has readl/writel_be, so the + * definition below can die once the STB04xxx support is + * finally ported over. + */ +#if defined(CONFIG_PPC) && !defined(CONFIG_PPC_MERGE) +#define readl_be(addr) in_be32((__force unsigned *)addr) +#define writel_be(val, addr) out_be32((__force unsigned *)addr, val) +#endif + +static inline unsigned int admhc_readl(const struct admhcd *ahcd, + __hc32 __iomem *regs) +{ +#ifdef CONFIG_USB_ADMHC_BIG_ENDIAN_MMIO + return big_endian_mmio(ahcd) ? + readl_be(regs) : + readl(regs); +#else + return readl(regs); +#endif +} + +static inline void admhc_writel(const struct admhcd *ahcd, + const unsigned int val, __hc32 __iomem *regs) +{ +#ifdef CONFIG_USB_ADMHC_BIG_ENDIAN_MMIO + big_endian_mmio(ahcd) ? + writel_be(val, regs) : + writel(val, regs); +#else + writel(val, regs); +#endif +} + +static inline void admhc_writel_flush(const struct admhcd *ahcd) +{ +#if 0 /* TODO: needed? */ + (void) admhc_readl(ahcd, &ahcd->regs->control); +#endif +} + + +/*-------------------------------------------------------------------------*/ + +/* cpu to ahcd */ +static inline __hc16 cpu_to_hc16(const struct admhcd *ahcd, const u16 x) +{ + return big_endian_desc(ahcd) ? + (__force __hc16)cpu_to_be16(x) : + (__force __hc16)cpu_to_le16(x); +} + +static inline __hc16 cpu_to_hc16p(const struct admhcd *ahcd, const u16 *x) +{ + return big_endian_desc(ahcd) ? + cpu_to_be16p(x) : + cpu_to_le16p(x); +} + +static inline __hc32 cpu_to_hc32(const struct admhcd *ahcd, const u32 x) +{ + return big_endian_desc(ahcd) ? + (__force __hc32)cpu_to_be32(x) : + (__force __hc32)cpu_to_le32(x); +} + +static inline __hc32 cpu_to_hc32p(const struct admhcd *ahcd, const u32 *x) +{ + return big_endian_desc(ahcd) ? + cpu_to_be32p(x) : + cpu_to_le32p(x); +} + +/* ahcd to cpu */ +static inline u16 hc16_to_cpu(const struct admhcd *ahcd, const __hc16 x) +{ + return big_endian_desc(ahcd) ? + be16_to_cpu((__force __be16)x) : + le16_to_cpu((__force __le16)x); +} + +static inline u16 hc16_to_cpup(const struct admhcd *ahcd, const __hc16 *x) +{ + return big_endian_desc(ahcd) ? + be16_to_cpup((__force __be16 *)x) : + le16_to_cpup((__force __le16 *)x); +} + +static inline u32 hc32_to_cpu(const struct admhcd *ahcd, const __hc32 x) +{ + return big_endian_desc(ahcd) ? + be32_to_cpu((__force __be32)x) : + le32_to_cpu((__force __le32)x); +} + +static inline u32 hc32_to_cpup(const struct admhcd *ahcd, const __hc32 *x) +{ + return big_endian_desc(ahcd) ? + be32_to_cpup((__force __be32 *)x) : + le32_to_cpup((__force __le32 *)x); +} + +/*-------------------------------------------------------------------------*/ + +static inline u16 admhc_frame_no(const struct admhcd *ahcd) +{ + u32 t; + + t = admhc_readl(ahcd, &ahcd->regs->fmnumber) & ADMHC_SFN_FN_MASK; + return (u16)t; +} + +/*-------------------------------------------------------------------------*/ + +static inline void admhc_disable(struct admhcd *ahcd) +{ + admhcd_to_hcd(ahcd)->state = HC_STATE_HALT; +} + +#define FI 0x2edf /* 12000 bits per frame (-1) */ +#define FSLDP(fi) (0x7fff & ((6 * ((fi) - 210)) / 7)) +#define FIT ADMHC_SFI_FIT +#define LSTHRESH 0x628 /* lowspeed bit threshold */ + +static inline void periodic_reinit(struct admhcd *ahcd) +{ + u32 fi = ahcd->fminterval & ADMHC_SFI_FI_MASK; + u32 fit = admhc_readl(ahcd, &ahcd->regs->fminterval) & FIT; + + /* TODO: adjust FSLargestDataPacket value too? */ + admhc_writel(ahcd, (fit ^ FIT) | ahcd->fminterval, + &ahcd->regs->fminterval); +} + +static inline u32 admhc_get_rhdesc(struct admhcd *ahcd) +{ + return admhc_readl(ahcd, &ahcd->regs->rhdesc); +} + +static inline u32 admhc_get_portstatus(struct admhcd *ahcd, int port) +{ + return admhc_readl(ahcd, &ahcd->regs->portstatus[port]); +} + +static inline void roothub_write_status(struct admhcd *ahcd, u32 value) +{ + /* FIXME: read-only bits must be masked out */ + admhc_writel(ahcd, value, &ahcd->regs->rhdesc); +} + +static inline void admhc_intr_disable(struct admhcd *ahcd, u32 ints) +{ + u32 t; + + t = admhc_readl(ahcd, &ahcd->regs->int_enable); + t &= ~(ints); + admhc_writel(ahcd, t, &ahcd->regs->int_enable); + /* TODO: flush writes ?*/ +} + +static inline void admhc_intr_enable(struct admhcd *ahcd, u32 ints) +{ + u32 t; + + t = admhc_readl(ahcd, &ahcd->regs->int_enable); + t |= ints; + admhc_writel(ahcd, t, &ahcd->regs->int_enable); + /* TODO: flush writes ?*/ +} + +static inline void admhc_intr_ack(struct admhcd *ahcd, u32 ints) +{ + admhc_writel(ahcd, ints, &ahcd->regs->int_status); +} + +static inline void admhc_dma_enable(struct admhcd *ahcd) +{ + ahcd->host_control = admhc_readl(ahcd, &ahcd->regs->host_control); + if (ahcd->host_control & ADMHC_HC_DMAE) + return; + + ahcd->host_control |= ADMHC_HC_DMAE; + admhc_writel(ahcd, ahcd->host_control, &ahcd->regs->host_control); +} + +static inline void admhc_dma_disable(struct admhcd *ahcd) +{ + ahcd->host_control = admhc_readl(ahcd, &ahcd->regs->host_control); + ahcd->host_control &= ~ADMHC_HC_DMAE; + admhc_writel(ahcd, ahcd->host_control, &ahcd->regs->host_control); +} |