diff options
author | juhosg <juhosg@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2008-10-05 11:07:49 +0000 |
---|---|---|
committer | juhosg <juhosg@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2008-10-05 11:07:49 +0000 |
commit | b06573b702b0adb42ce4c798f2915a280c17eeed (patch) | |
tree | 3156be9bf73898cb713f1a820f4c487c879d616f /target/linux/adm5120/files/drivers/usb/host | |
parent | 7bab42f05f60806bf4b7ba63688d96b68142c80b (diff) |
[adm5120] experimental support for 2.6.27
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@12863 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/adm5120/files/drivers/usb/host')
8 files changed, 4728 insertions, 0 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..c121f7be2 --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-dbg.c @@ -0,0 +1,842 @@ +/* + * ADM5120 HCD (Host Controller Driver) for USB + * + * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> + * + * This file was derived from: drivers/usb/host/ohci-dbg.c + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +/*-------------------------------------------------------------------------*/ + +static inline char *ed_typestring(int ed_type) +{ + switch (ed_type) { + case PIPE_CONTROL: + return "ctrl"; + case PIPE_BULK: + return "bulk"; + case PIPE_INTERRUPT: + return "intr"; + case PIPE_ISOCHRONOUS: + return "isoc"; + } + return "(bad ed_type)"; +} + +static inline char *ed_statestring(int state) +{ + switch (state) { + case ED_IDLE: + return "IDLE"; + case ED_UNLINK: + return "UNLINK"; + case ED_OPER: + return "OPER"; + } + return "?STATE"; +} + +static inline char *pipestring(int pipe) +{ + return ed_typestring(usb_pipetype(pipe)); +} + +static inline char *td_pidstring(u32 info) +{ + switch (info & TD_DP) { + case TD_DP_SETUP: + return "SETUP"; + case TD_DP_IN: + return "IN"; + case TD_DP_OUT: + return "OUT"; + } + return "?PID"; +} + +static inline char *td_togglestring(u32 info) +{ + switch (info & TD_T) { + case TD_T_DATA0: + return "DATA0"; + case TD_T_DATA1: + return "DATA1"; + case TD_T_CARRY: + return "CARRY"; + } + return "?TOGGLE"; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG + +/* debug| print the main components of an URB + * small: 0) header + data packets 1) just header + */ +static void __attribute__((unused)) +urb_print(struct admhcd *ahcd, struct urb *urb, char *str, int small, int status) +{ + unsigned int pipe = urb->pipe; + + if (!urb->dev || !urb->dev->bus) { + admhc_dbg(ahcd, "%s URB: no dev", str); + return; + } + +#ifndef ADMHC_VERBOSE_DEBUG + if (status != 0) +#endif + admhc_dbg(ahcd, "URB-%s %p dev=%d ep=%d%s-%s flags=%x len=%d/%d " + "stat=%d\n", + 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, + status); + +#ifdef ADMHC_VERBOSE_DEBUG + if (!small) { + int i, len; + + if (usb_pipecontrol(pipe)) { + admhc_dbg(ahcd, "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) { + admhc_dbg(ahcd, "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? "...": "", 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%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_7) ? " !7!" : "", + (mask & ADMHC_INTR_6) ? " !6!" : "", + (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 "?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_read_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_read_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; + + 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)); + + tmp = hc32_to_cpup(ahcd, &td->hwINFO); + 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), + td_togglestring(tmp), + td_pidstring(tmp), + TD_ISI_GET(tmp), + TD_FN_GET(tmp)); + + 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 %s type %s; next ed %08x\n", + label, + ed, ed_statestring(ed->state), ed_typestring(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 admhcd *ahcd, 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_td(const struct admhcd *ahcd, const char *label, + const struct td *td) {} +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 int debug_async_open(struct inode *, struct file *); +static int debug_periodic_open(struct inode *, struct file *); +static int debug_registers_open(struct inode *, struct file *); +static ssize_t debug_output(struct file*, char __user*, size_t, loff_t*); +static int debug_close(struct inode *, struct file *); + +static const struct file_operations debug_async_fops = { + .owner = THIS_MODULE, + .open = debug_async_open, + .read = debug_output, + .release = debug_close, +}; +static const struct file_operations debug_periodic_fops = { + .owner = THIS_MODULE, + .open = debug_periodic_open, + .read = debug_output, + .release = debug_close, +}; +static const struct file_operations debug_registers_fops = { + .owner = THIS_MODULE, + .open = debug_registers_open, + .read = debug_output, + .release = debug_close, +}; + +static struct dentry *admhc_debug_root; + +struct debug_buffer { + ssize_t (*fill_func)(struct debug_buffer *); /* fill method */ + struct device *dev; + struct mutex mutex; /* protect filling of buffer */ + size_t count; /* number of characters filled into buffer */ + char *page; +}; + +static ssize_t +show_list(struct admhcd *ahcd, char *buf, size_t count, struct ed *ed) +{ + unsigned temp; + unsigned size = count; + + if (!ed) + return 0; + + /* 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); + u32 tailp = hc32_to_cpu(ahcd, ed->hwTailP); + struct list_head *entry; + struct td *td; + + temp = scnprintf(buf, size, + "ed/%p %s %s %cs dev%d ep%d %s%smax %d %08x%s%s %s" + " h:%08x t:%08x", + ed, + ed_statestring(ed->state), + ed_typestring (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, + headp & ED_MASK,tailp); + 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, + td_pidstring(info), + 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 fill_async_buffer(struct debug_buffer *buf) +{ + struct usb_bus *bus; + struct usb_hcd *hcd; + struct admhcd *ahcd; + size_t temp; + unsigned long flags; + + bus = dev_get_drvdata(buf->dev); + hcd = bus_to_hcd(bus); + ahcd = hcd_to_admhcd(hcd); + + spin_lock_irqsave(&ahcd->lock, flags); + temp = show_list(ahcd, buf->page, PAGE_SIZE, ahcd->ed_head); + spin_unlock_irqrestore(&ahcd->lock, flags); + + return temp; +} + + +#define DBG_SCHED_LIMIT 64 + +static ssize_t fill_periodic_buffer(struct debug_buffer *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 = dev_get_drvdata(buf->dev); + hcd = bus_to_hcd(bus); + ahcd = hcd_to_admhcd(hcd); + next = buf->page; + 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; +} + + +#undef DBG_SCHED_LIMIT + +static ssize_t fill_registers_buffer(struct debug_buffer *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 = dev_get_drvdata(buf->dev); + hcd = bus_to_hcd(bus); + ahcd = hcd_to_admhcd(hcd); + regs = ahcd->regs; + next = buf->page; + 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 struct debug_buffer *alloc_buffer(struct device *dev, + ssize_t (*fill_func)(struct debug_buffer *)) +{ + struct debug_buffer *buf; + + buf = kzalloc(sizeof(struct debug_buffer), GFP_KERNEL); + + if (buf) { + buf->dev = dev; + buf->fill_func = fill_func; + mutex_init(&buf->mutex); + } + + return buf; +} + +static int fill_buffer(struct debug_buffer *buf) +{ + int ret = 0; + + if (!buf->page) + buf->page = (char *)get_zeroed_page(GFP_KERNEL); + + if (!buf->page) { + ret = -ENOMEM; + goto out; + } + + ret = buf->fill_func(buf); + + if (ret >= 0) { + buf->count = ret; + ret = 0; + } + +out: + return ret; +} + +static ssize_t debug_output(struct file *file, char __user *user_buf, + size_t len, loff_t *offset) +{ + struct debug_buffer *buf = file->private_data; + int ret = 0; + + mutex_lock(&buf->mutex); + if (buf->count == 0) { + ret = fill_buffer(buf); + if (ret != 0) { + mutex_unlock(&buf->mutex); + goto out; + } + } + mutex_unlock(&buf->mutex); + + ret = simple_read_from_buffer(user_buf, len, offset, + buf->page, buf->count); + +out: + return ret; +} + +static int debug_close(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf = file->private_data; + + if (buf) { + if (buf->page) + free_page((unsigned long)buf->page); + kfree(buf); + } + + return 0; +} + +static int debug_async_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static int debug_periodic_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_periodic_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static int debug_registers_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_registers_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static inline void create_debug_files(struct admhcd *ahcd) +{ + struct usb_bus *bus = &admhcd_to_hcd(ahcd)->self; + struct device *dev = bus->dev; + + ahcd->debug_dir = debugfs_create_dir(bus->bus_name, admhc_debug_root); + if (!ahcd->debug_dir) + goto dir_error; + + ahcd->debug_async = debugfs_create_file("async", S_IRUGO, + ahcd->debug_dir, dev, + &debug_async_fops); + if (!ahcd->debug_async) + goto async_error; + + ahcd->debug_periodic = debugfs_create_file("periodic", S_IRUGO, + ahcd->debug_dir, dev, + &debug_periodic_fops); + if (!ahcd->debug_periodic) + goto periodic_error; + + ahcd->debug_registers = debugfs_create_file("registers", S_IRUGO, + ahcd->debug_dir, dev, + &debug_registers_fops); + if (!ahcd->debug_registers) + goto registers_error; + + admhc_dbg(ahcd, "created debug files\n"); + return; + +registers_error: + debugfs_remove(ahcd->debug_periodic); +periodic_error: + debugfs_remove(ahcd->debug_async); +async_error: + debugfs_remove(ahcd->debug_dir); +dir_error: + ahcd->debug_periodic = NULL; + ahcd->debug_async = NULL; + ahcd->debug_dir = NULL; +} + +static inline void remove_debug_files(struct admhcd *ahcd) +{ + debugfs_remove(ahcd->debug_registers); + debugfs_remove(ahcd->debug_periodic); + debugfs_remove(ahcd->debug_async); + debugfs_remove(ahcd->debug_dir); +} + +#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..bf250c056 --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-drv.c @@ -0,0 +1,236 @@ +/* + * ADM5120 HCD (Host Controller Driver) for USB + * + * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> + * + * This file was derived from: drivers/usb/host/ohci-au1xxx.c + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * (C) Copyright 2002 Hewlett-Packard Company + * + * 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 program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#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_number, + + /* + * root hub support + */ + .hub_status_data = admhc_hub_status_data, + .hub_control = admhc_hub_control, + .hub_irq_enable = admhc_hub_irq_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; + + 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; +} + +#ifdef CONFIG_PM +/* 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; +} +#else +#define usb_hcd_adm5120_suspend NULL +#define usb_hcd_adm5120_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver usb_hcd_adm5120_driver = { + .probe = usb_hcd_adm5120_probe, + .remove = usb_hcd_adm5120_remove, + .shutdown = usb_hcd_platform_shutdown, + .suspend = usb_hcd_adm5120_suspend, + .resume = usb_hcd_adm5120_resume, + .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 new file mode 100644 index 000000000..c947a6c2d --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-hcd.c @@ -0,0 +1,845 @@ +/* + * ADM5120 HCD (Host Controller Driver) for USB + * + * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> + * + * This file was derived from: drivers/usb/host/ohci-hcd.c + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net> + * + * [ Initialisation is based on Linus' ] + * [ uhci code and gregs ahcd fragments ] + * [ (C) Copyright 1999 Linus Torvalds ] + * [ (C) Copyright 1999 Gregory P. Smith] + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/delay.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/usb/otg.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/reboot.h> +#include <linux/debugfs.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> +#include <asm/byteorder.h> + +#include "../core/hcd.h" +#include "../core/hub.h" + +#define DRIVER_VERSION "0.25.0" +#define DRIVER_AUTHOR "Gabor Juhos <juhosg@openwrt.org>" +#define DRIVER_DESC "ADMtek USB 1.1 Host Controller Driver" + +/*-------------------------------------------------------------------------*/ + +#undef ADMHC_VERBOSE_DEBUG /* not always helpful */ + +/* For initializing controller (mask in an HCFS mode too) */ +#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR + +#define ADMHC_INTR_INIT \ + ( ADMHC_INTR_MIE | ADMHC_INTR_INSM | ADMHC_INTR_FATI \ + | ADMHC_INTR_RESI | ADMHC_INTR_TDC | ADMHC_INTR_BABI ) + +/*-------------------------------------------------------------------------*/ + +static const char hcd_name [] = "admhc-hcd"; + +#define STATECHANGE_DELAY msecs_to_jiffies(300) + +#include "adm5120.h" + +static void admhc_dump(struct admhcd *ahcd, int verbose); +static int admhc_init(struct admhcd *ahcd); +static void admhc_stop(struct usb_hcd *hcd); + +#include "adm5120-dbg.c" +#include "adm5120-mem.c" +#include "adm5120-pm.c" +#include "adm5120-hub.c" +#include "adm5120-q.c" + +/*-------------------------------------------------------------------------*/ + +/* + * queue up an urb for anything except the root hub + */ +static int admhc_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + struct ed *ed; + struct urb_priv *urb_priv; + unsigned int pipe = urb->pipe; + int td_cnt = 0; + unsigned long flags; + int ret = 0; + +#ifdef ADMHC_VERBOSE_DEBUG + spin_lock_irqsave(&ahcd->lock, flags); + urb_print(ahcd, urb, "ENQEUE", usb_pipein(pipe), -EINPROGRESS); + spin_unlock_irqrestore(&ahcd->lock, flags); +#endif + + /* every endpoint has an ed, locate and maybe (re)initialize it */ + ed = ed_get(ahcd, urb->ep, urb->dev, pipe, urb->interval); + if (!ed) + return -ENOMEM; + + /* for the private part of the URB we need the number of TDs */ + switch (ed->type) { + case PIPE_CONTROL: + 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: + /* 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: + /* number of packets from URB */ + td_cnt = urb->number_of_packets; + break; + } + + 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)) { + ret = -ENODEV; + goto fail; + } + if (!HC_IS_RUNNING(hcd->state)) { + ret = -ENODEV; + goto fail; + } + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto fail; + + /* schedule the ed if needed */ + if (ed->state == ED_IDLE) { + ret = ed_schedule(ahcd, ed); + if (ret < 0) { + usb_hcd_unlink_urb_from_ep(hcd, urb); + goto fail; + } + 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. + */ + } + } 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 ADMHC_VERBOSE_DEBUG + admhc_dump_ed(ahcd, "admhc_urb_enqueue", urb_priv->ed, 1); +#endif + +fail: + if (ret) + urb_priv_free(ahcd, urb_priv); + + spin_unlock_irqrestore(&ahcd->lock, flags); + return ret; +} + +/* + * decouple the URB from the HC queues (TDs, urb_priv); + * 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, + int status) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ahcd->lock, flags); + +#ifdef ADMHC_VERBOSE_DEBUG + urb_print(ahcd, urb, "DEQUEUE", 1, status); +#endif + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) { + /* Do nothing */ + ; + } else 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, status); + } + spin_unlock_irqrestore(&ahcd->lock, flags); + + return ret; +} + +/*-------------------------------------------------------------------------*/ + +/* 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); + 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 + +rescan: + spin_lock_irqsave(&ahcd->lock, flags); + + if (!HC_IS_RUNNING(hcd->state)) { +sanitize: + ed->state = ED_IDLE; + finish_unlinks(ahcd, 0); + } + + 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; + } + /* 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; + } + + ep->hcpriv = NULL; + + spin_unlock_irqrestore(&ahcd->lock, flags); + return; +} + +static int admhc_get_frame_number(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + + return admhc_frame_no(ahcd); +} + +static void admhc_usb_reset(struct admhcd *ahcd) +{ +#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 +} + +/* 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; + + 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); +} + +/*-------------------------------------------------------------------------* + * 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; + } + + if (ahcd->ed_tails[PIPE_ISOCHRONOUS]) { + ed_free(ahcd, ahcd->ed_tails[PIPE_ISOCHRONOUS]); + ahcd->ed_tails[PIPE_ISOCHRONOUS] = NULL; + } + + if (ahcd->ed_tails[PIPE_CONTROL]) { + ed_free(ahcd, ahcd->ed_tails[PIPE_CONTROL]); + ahcd->ed_tails[PIPE_CONTROL] = NULL; + } + + if (ahcd->ed_tails[PIPE_BULK]) { + ed_free(ahcd, ahcd->ed_tails[PIPE_BULK]); + ahcd->ed_tails[PIPE_BULK] = NULL; + } + + ahcd->ed_head = NULL; +} + +#define ED_DUMMY_INFO (ED_SPEED_FULL | ED_SKIP) + +static int admhc_eds_init(struct admhcd *ahcd) +{ + struct ed *ed; + + ed = ed_create(ahcd, PIPE_INTERRUPT, ED_DUMMY_INFO); + if (!ed) + goto err; + + ahcd->ed_tails[PIPE_INTERRUPT] = ed; + + ed = ed_create(ahcd, PIPE_ISOCHRONOUS, ED_DUMMY_INFO); + if (!ed) + goto err; + + 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); + + ed = ed_create(ahcd, PIPE_CONTROL, ED_DUMMY_INFO); + if (!ed) + goto err; + + 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); + + ed = ed_create(ahcd, PIPE_BULK, ED_DUMMY_INFO); + if (!ed) + goto err; + + 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; +} + +/* init memory, and kick BIOS/SMM off */ + +static int admhc_init(struct admhcd *ahcd) +{ + struct usb_hcd *hcd = admhcd_to_hcd(ahcd); + int ret; + + admhc_disable(ahcd); + ahcd->regs = hcd->regs; + + /* Disable HC interrupts */ + admhc_intr_disable(ahcd, ADMHC_INTR_MIE); + + /* Read the number of ports unless overridden */ + if (ahcd->num_ports == 0) + ahcd->num_ports = admhc_read_rhdesc(ahcd) & ADMHC_RH_NUMP; + + 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; +} + +/*-------------------------------------------------------------------------*/ + +/* Start an OHCI controller, set the BUS operational + * resets USB and controller + * enable interrupts + */ +static int admhc_run(struct admhcd *ahcd) +{ + 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 */ + } + +#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 + + 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_read_rhdesc(ahcd); + if (!(temp & ADMHC_RH_NPS)) { + /* power down each port */ + for (temp = 0; temp < ahcd->num_ports; temp++) + admhc_write_portstatus(ahcd, temp, ADMHC_PS_CPP); + } + /* flush those writes */ + admhc_writel_flush(ahcd); + + /* 2msec timelimit here means no irqs/preempt */ + spin_lock_irq(&ahcd->lock); + + 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); + } + + /* enable HOST mode, before access any host specific register */ + admhc_writel(ahcd, ADMHC_CTRL_UHFE, &ahcd->regs->gencontrol); + + /* Tell the controller where the descriptor list is */ + admhc_writel(ahcd, (u32)ahcd->ed_head->dma, &ahcd->regs->hosthead); + + periodic_reinit(ahcd); + + /* use rhsc irqs after khubd is fully initialized */ + hcd->poll_rh = 1; + hcd->uses_new_polling = 1; + +#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 + + /* 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); + + /* flush those writes */ + admhc_writel_flush(ahcd); + + /* start controller operations */ + ahcd->host_control = ADMHC_BUSS_OPER; + admhc_writel(ahcd, ahcd->host_control, &ahcd->regs->host_control); + + 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); + } + + hcd->state = HC_STATE_RUNNING; + + 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); + } + +#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; + } + + 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); + } + + 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"); + } + + 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); + } + + 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); + } + + if (ints & ADMHC_INTR_SO) { + /* could track INTR_SO to reduce available PCI/... bandwidth */ + admhc_vdbg(ahcd, "Schedule Overrun\n"); + } + +#if 1 + spin_lock(&ahcd->lock); + if (ahcd->ed_rm_list) + finish_unlinks(ahcd, admhc_frame_no(ahcd)); + + 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 + + if (HC_IS_RUNNING(hcd->state)) { + admhc_intr_ack(ahcd, ints); + admhc_intr_enable(ahcd, ADMHC_INTR_MIE); + admhc_writel_flush(ahcd); + } + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static void admhc_stop(struct usb_hcd *hcd) +{ + 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); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_ADM5120 +#include "adm5120-drv.c" +#define PLATFORM_DRIVER usb_hcd_adm5120_driver +#endif + +#if !defined(PLATFORM_DRIVER) +#error "missing bus glue for admhc-hcd" +#endif + +#define DRIVER_INFO DRIVER_DESC " version " DRIVER_VERSION + +static int __init admhc_hcd_mod_init(void) +{ + int ret = 0; + + if (usb_disabled()) + 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)); + +#ifdef DEBUG + admhc_debug_root = debugfs_create_dir("admhc", NULL); + if (!admhc_debug_root) { + ret = -ENOENT; + goto error_debug; + } +#endif + +#ifdef PLATFORM_DRIVER + ret = platform_driver_register(&PLATFORM_DRIVER); + if (ret < 0) + goto error_platform; +#endif + + return ret; + +#ifdef PLATFORM_DRIVER + platform_driver_unregister(&PLATFORM_DRIVER); +error_platform: +#endif + +#ifdef DEBUG + debugfs_remove(admhc_debug_root); + admhc_debug_root = NULL; +error_debug: +#endif + return ret; +} +module_init(admhc_hcd_mod_init); + +static void __exit admhc_hcd_mod_exit(void) +{ + platform_driver_unregister(&PLATFORM_DRIVER); +#ifdef DEBUG + debugfs_remove(admhc_debug_root); +#endif +} +module_exit(admhc_hcd_mod_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); 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..6ba62050b --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-hub.c @@ -0,0 +1,441 @@ +/* + * ADM5120 HCD (Host Controller Driver) for USB + * + * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> + * + * This file was derived from: drivers/usb/host/ohci-hub.c + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +/*-------------------------------------------------------------------------*/ + +/* + * ADM5120 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, value, \ + (value & ADMHC_PS_PRSC) ? " PRSC" : "", \ + (value & ADMHC_PS_OCIC) ? " OCIC" : "", \ + (value & ADMHC_PS_PSSC) ? " PSSC" : "", \ + (value & ADMHC_PS_PESC) ? " PESC" : "", \ + (value & ADMHC_PS_CSC) ? " CSC" : "", \ + \ + (value & ADMHC_PS_LSDA) ? " LSDA" : "", \ + (value & ADMHC_PS_PPS) ? " PPS" : "", \ + (value & ADMHC_PS_PRS) ? " PRS" : "", \ + (value & ADMHC_PS_POCI) ? " POCI" : "", \ + (value & ADMHC_PS_PSS) ? " PSS" : "", \ + \ + (value & ADMHC_PS_PES) ? " PES" : "", \ + (value & 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, value, \ + (value & ADMHC_PS_PRSC) ? " PRSC" : "", \ + (value & ADMHC_PS_OCIC) ? " OCIC" : "", \ + (value & ADMHC_PS_PSSC) ? " PSSC" : "", \ + (value & ADMHC_PS_PESC) ? " PESC" : "", \ + (value & ADMHC_PS_CSC) ? " CSC" : "", \ + \ + (value & ADMHC_PS_CPP) ? " CPP" : "", \ + (value & ADMHC_PS_SPP) ? " SPP" : "", \ + (value & ADMHC_PS_SPR) ? " SPR" : "", \ + (value & ADMHC_PS_CPS) ? " CPS" : "", \ + (value & ADMHC_PS_SPS) ? " SPS" : "", \ + \ + (value & ADMHC_PS_SPE) ? " SPE" : "", \ + (value & ADMHC_PS_CPE) ? " CPE" : "" \ + ); + +/*-------------------------------------------------------------------------*/ + +/* hcd->hub_irq_enable() */ +static void admhc_hub_irq_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); +} + +/*-------------------------------------------------------------------------*/ + +/* 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_read_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_read_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 int admhc_get_hub_descriptor(struct admhcd *ahcd, char *buf) +{ + struct usb_hub_descriptor *desc = (struct usb_hub_descriptor *)buf; + u32 rh = admhc_read_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; + + return 0; +} + +static int admhc_get_hub_status(struct admhcd *ahcd, char *buf) +{ + struct usb_hub_status *hs = (struct usb_hub_status *)buf; + u32 t = admhc_read_rhdesc(ahcd); + u16 status, change; + + status = 0; + status |= (t & ADMHC_RH_LPS) ? HUB_STATUS_LOCAL_POWER : 0; + status |= (t & ADMHC_RH_OCI) ? HUB_STATUS_OVERCURRENT : 0; + + change = 0; + change |= (t & ADMHC_RH_LPSC) ? HUB_CHANGE_LOCAL_POWER : 0; + change |= (t & ADMHC_RH_OCIC) ? HUB_CHANGE_OVERCURRENT : 0; + + hs->wHubStatus = (__force __u16)cpu_to_hc16(ahcd, status); + hs->wHubChange = (__force __u16)cpu_to_hc16(ahcd, change); + + return 0; +} + +static int admhc_get_port_status(struct admhcd *ahcd, unsigned port, char *buf) +{ + struct usb_port_status *ps = (struct usb_port_status *)buf; + u32 t = admhc_read_portstatus(ahcd, port); + u16 status, change; + + status = 0; + status |= (t & ADMHC_PS_CCS) ? USB_PORT_STAT_CONNECTION : 0; + status |= (t & ADMHC_PS_PES) ? USB_PORT_STAT_ENABLE : 0; + status |= (t & ADMHC_PS_PSS) ? USB_PORT_STAT_SUSPEND : 0; + status |= (t & ADMHC_PS_POCI) ? USB_PORT_STAT_OVERCURRENT : 0; + status |= (t & ADMHC_PS_PRS) ? USB_PORT_STAT_RESET : 0; + status |= (t & ADMHC_PS_PPS) ? USB_PORT_STAT_POWER : 0; + status |= (t & ADMHC_PS_LSDA) ? USB_PORT_STAT_LOW_SPEED : 0; + + change = 0; + change |= (t & ADMHC_PS_CSC) ? USB_PORT_STAT_C_CONNECTION : 0; + change |= (t & ADMHC_PS_PESC) ? USB_PORT_STAT_C_ENABLE : 0; + change |= (t & ADMHC_PS_PSSC) ? USB_PORT_STAT_C_SUSPEND : 0; + change |= (t & ADMHC_PS_OCIC) ? USB_PORT_STAT_C_OVERCURRENT : 0; + change |= (t & ADMHC_PS_PRSC) ? USB_PORT_STAT_C_RESET : 0; + + ps->wPortStatus = (__force __u16)cpu_to_hc16(ahcd, status); + ps->wPortChange = (__force __u16)cpu_to_hc16(ahcd, change); + + return 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_read_portstatus(ahcd, port); + if (!(status & ADMHC_PS_CCS)) + return -ENODEV; + + /* khubd will finish the reset later */ + admhc_write_portstatus(ahcd, port, ADMHC_PS_PRS); + 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 admhc_port_reset(struct admhcd *ahcd, unsigned port) +{ + u32 t; + + admhc_vdbg(ahcd, "reset port%d\n", port); + t = admhc_read_portstatus(ahcd, port); + if (!(t & ADMHC_PS_CCS)) + return -ENODEV; + + admhc_write_portstatus(ahcd, port, ADMHC_PS_SPR); + mdelay(10); + admhc_write_portstatus(ahcd, port, (ADMHC_PS_SPE | ADMHC_PS_CSC)); + mdelay(100); + + return 0; +} + +static inline int admhc_port_enable(struct admhcd *ahcd, unsigned port) +{ + u32 t; + + admhc_vdbg(ahcd, "enable port%d\n", port); + t = admhc_read_portstatus(ahcd, port); + if (!(t & ADMHC_PS_CCS)) + return -ENODEV; + + admhc_write_portstatus(ahcd, port, ADMHC_PS_SPE); + + return 0; +} + +static inline int admhc_port_disable(struct admhcd *ahcd, unsigned port) +{ + u32 t; + + admhc_vdbg(ahcd, "disable port%d\n", port); + t = admhc_read_portstatus(ahcd, port); + if (!(t & ADMHC_PS_CCS)) + return -ENODEV; + + admhc_write_portstatus(ahcd, port, ADMHC_PS_CPE); + + return 0; +} + +static inline int admhc_port_write(struct admhcd *ahcd, unsigned port, + u32 val) +{ +#ifdef ADMHC_VERBOSE_DEBUG + dbg_port_write(ahcd, "write", port, val); +#endif + admhc_write_portstatus(ahcd, port, val); + + 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; + int ret = 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: + ret = admhc_port_disable(ahcd, wIndex); + break; + case USB_PORT_FEAT_SUSPEND: + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CPS); + break; + case USB_PORT_FEAT_POWER: + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CPP); + break; + case USB_PORT_FEAT_C_CONNECTION: + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_CSC); + break; + case USB_PORT_FEAT_C_ENABLE: + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PESC); + break; + case USB_PORT_FEAT_C_SUSPEND: + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PSSC); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_OCIC); + break; + case USB_PORT_FEAT_C_RESET: + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_PRSC); + break; + default: + goto error; + } + break; + case GetHubDescriptor: + ret = admhc_get_hub_descriptor(ahcd, buf); + break; + case GetHubStatus: + ret = admhc_get_hub_status(ahcd, buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + + ret = admhc_get_port_status(ahcd, wIndex, buf); + 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: + ret = admhc_port_enable(ahcd, wIndex); + break; + case USB_PORT_FEAT_RESET: + ret = admhc_port_reset(ahcd, 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 + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_SPS); + break; + case USB_PORT_FEAT_POWER: + ret = admhc_port_write(ahcd, wIndex, ADMHC_PS_SPP); + break; + default: + goto error; + } + break; + + default: +error: + /* "protocol stall" on error */ + ret = -EPIPE; + } + + return ret; +} + 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..da50d42eb --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-mem.c @@ -0,0 +1,202 @@ +/* + * ADM5120 HCD (Host Controller Driver) for USB + * + * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> + * + * This file was derived from: drivers/usb/host/ohci-mem.c + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +/*-------------------------------------------------------------------------*/ + +/* + * 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-pm.c b/target/linux/adm5120/files/drivers/usb/host/adm5120-pm.c new file mode 100644 index 000000000..9d49664cb --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-pm.c @@ -0,0 +1,445 @@ +/* + * ADM5120 HCD (Host Controller Driver) for USB + * + * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> + * + * This file was derived from fragments of the OHCI driver. + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#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; +} + +/*-------------------------------------------------------------------------*/ + +/* must not be called from interrupt context */ +static int admhc_restart(struct admhcd *ahcd) +{ + 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); + } + + if (!urb->unlinked) + urb->unlinked = -ESHUTDOWN; + } + 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 /* 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 */ + 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..4c742c0f3 --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120-q.c @@ -0,0 +1,955 @@ +/* + * ADM5120 HCD (Host Controller Driver) for USB + * + * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> + * + * This file was derived from: drivers/usb/host/ohci-q.c + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#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, int status) +__releases(ahcd->lock) +__acquires(ahcd->lock) +{ + urb_priv_free(ahcd, urb->hcpriv); + + if (likely(status == -EINPROGRESS)) + status = 0; + + 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(ahcd, urb, "RET", usb_pipeout (urb->pipe), status); +#endif + + /* urb->complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(admhcd_to_hcd(ahcd), urb); + spin_unlock(&ahcd->lock); + usb_hcd_giveback_urb(admhcd_to_hcd(ahcd), urb, status); + 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) +{ + +#ifdef ADMHC_VERBOSE_DEBUG + admhc_dump_ed(ahcd, "ED-DESCHED", ed, 1); +#endif + + 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) +{ + +#ifdef ADMHC_VERBOSE_DEBUG + admhc_dump_ed(ahcd, "ED-UNLINK", ed, 1); +#endif + + 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); + u32 toggle = 0; + + /* 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 = is_out + ? TD_T_CARRY | TD_SCC_NOTACCESSED | TD_DP_OUT + : TD_T_CARRY | TD_SCC_NOTACCESSED | TD_DP_IN; + + 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 */ +static int td_done(struct admhcd *ahcd, struct urb *urb, struct td *td) +{ + struct urb_priv *urb_priv = urb->hcpriv; + u32 info; + u32 bl; + u32 tdDBP; + int type = usb_pipetype(urb->pipe); + int cc; + int status = -EINPROGRESS; + + info = hc32_to_cpup(ahcd, &td->hwINFO); + tdDBP = hc32_to_cpup(ahcd, &td->hwDBP); + bl = TD_BL_GET(hc32_to_cpup(ahcd, &td->hwCBL)); + cc = TD_CC_GET(info); + + /* ISO ... drivers see per-TD length/status */ + if (type == PIPE_ISOCHRONOUS) { + /* TODO */ + int dlen = 0; + + /* NOTE: assumes FC in tdINFO == 0, and that + * only the first of 0..MAXPSW psws is used. + */ + if (info & TD_CC) /* hc didn't touch? */ + return status; + + if (usb_pipeout(urb->pipe)) + dlen = urb->iso_frame_desc[td->index].length; + else { + /* short reads are always OK for ISO */ + if (cc == TD_CC_DATAUNDERRUN) + cc = TD_CC_NOERROR; + dlen = tdDBP - td->data_dma + bl; + } + + 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); + + /* 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 { + /* 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) + status = cc_to_error[cc]; + + + /* 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); + urb_priv->td_idx++; + + return status; +} + +/*-------------------------------------------------------------------------*/ + +static inline void +ed_halted(struct admhcd *ahcd, struct td *td, int cc, struct td *rev) +{ + struct urb *urb = td->urb; + struct urb_priv *urb_priv = urb->hcpriv; + 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); + + /* Get rid of all later tds from this urb. We don't have + * to be careful: 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; + + 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. + */ + list_del(&next->td_list); + urb_priv->td_cnt++; + 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]); + } +} + +/*-------------------------------------------------------------------------*/ + +/* 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 0 + 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; + } +#endif + } + + /* 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; + int status; + + td = list_entry(entry, struct td, td_list); + urb = td->urb; + urb_priv = td->urb->hcpriv; + + if (!urb->unlinked) { + 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 */ +#ifdef ADMHC_VERBOSE_DEBUG + urb_print(ahcd, urb, "PARTIAL", 0); +#endif + status = td_done(ahcd, urb, td); + + /* if URB is done, clean up */ + if (urb_priv->td_idx == urb_priv->td_cnt) { + modified = completed = 1; + finish_urb(ahcd, urb, status); + } + } + 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_unhalt(struct admhcd *ahcd, struct ed *ed, struct urb *urb) +{ + struct list_head *entry,*tmp; + __hc32 toggle = ed->hwHeadP & cpu_to_hc32(ahcd, ED_C); + +#ifdef ADMHC_VERBOSE_DEBUG + admhc_dump_ed(ahcd, "UNHALT", ed, 0); +#endif + /* 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); + + list_for_each_safe(entry, tmp, &ed->td_list) { + struct td *td = list_entry(entry, struct td, td_list); + __hc32 info; + + if (td->urb != urb) + break; + + info = td->hwINFO; + info &= ~cpu_to_hc32(ahcd, TD_CC | TD_OWN); + td->hwINFO = info; + + ed->hwHeadP = td->hwNextTD | toggle; + wmb(); + } + +} + +static void ed_intr_refill(struct admhcd *ahcd, struct ed *ed) +{ + __hc32 toggle = ed->hwHeadP & cpu_to_hc32(ahcd, ED_C); + + ed->hwHeadP = ed->hwTailP | toggle; +} + + +static inline int is_ed_halted(struct admhcd *ahcd, struct ed *ed) +{ + return ((hc32_to_cpup(ahcd, &ed->hwHeadP) & ED_H) == ED_H); +} + +static inline int is_td_halted(struct admhcd *ahcd, struct ed *ed, + struct td *td) +{ + return ((hc32_to_cpup(ahcd, &ed->hwHeadP) & TD_MASK) == + (hc32_to_cpup(ahcd, &td->hwNextTD) & TD_MASK)); +} + +static void ed_update(struct admhcd *ahcd, struct ed *ed) +{ + struct list_head *entry,*tmp; + +#ifdef ADMHC_VERBOSE_DEBUG + admhc_dump_ed(ahcd, "UPDATE", ed, 1); +#endif + + 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; + int status; + + if (hc32_to_cpup(ahcd, &td->hwINFO) & TD_OWN) + break; + + /* update URB's length and status from TD */ + status = td_done(ahcd, urb, td); + if (is_ed_halted(ahcd, ed) && is_td_halted(ahcd, ed, td)) + ed_unhalt(ahcd, ed, urb); + + if (ed->type == PIPE_INTERRUPT) + ed_intr_refill(ahcd,ed); + + /* If all this urb's TDs are done, call complete() */ + if (urb_priv->td_idx == urb_priv->td_cnt) + finish_urb(ahcd, urb, status); + + /* 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 + } + + } +} + +/* 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; + + 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..7c47cb8b7 --- /dev/null +++ b/target/linux/adm5120/files/drivers/usb/host/adm5120.h @@ -0,0 +1,762 @@ +/* + * ADM5120 HCD (Host Controller Driver) for USB + * + * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> + * + * This file was derived from: drivers/usb/host/ohci.h + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +/* + * __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 */ + + /* 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 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_GET (((x) >> TD_DP_SHIFT) & TD_DP_MASK) +#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_6 (1 << 6) /* unknown */ +#define ADMHC_INTR_7 (1 << 7) /* unknown */ +#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 *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 + +#ifdef DEBUG + struct dentry *debug_dir; + struct dentry *debug_async; + struct dentry *debug_periodic; + struct dentry *debug_registers; +#endif +}; + +/* 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 */ + +#ifdef DEBUG +# define admhc_dbg(ahcd, fmt, args...) \ + printk(KERN_DEBUG "adm5120-hcd: " fmt , ## args ) +#else +# define admhc_dbg(ahcd, fmt, args...) do { } while (0) +#endif + +#define admhc_err(ahcd, fmt, args...) \ + printk(KERN_ERR "adm5120-hcd: " fmt , ## args ) +#define admhc_info(ahcd, fmt, args...) \ + printk(KERN_INFO "adm5120-hcd: " fmt , ## args ) +#define admhc_warn(ahcd, fmt, args...) \ + printk(KERN_WARNING "adm5120-hcd: " 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: remove? */ + (void) admhc_readl(ahcd, &ahcd->regs->gencontrol); +#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 u16 admhc_frame_remain(const struct admhcd *ahcd) +{ + u32 t; + + t = admhc_readl(ahcd, &ahcd->regs->fmnumber) >> ADMHC_SFN_FR_SHIFT; + t &= ADMHC_SFN_FR_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) - 1200)) / 7)) +#define FIT ADMHC_SFI_FIT +#define LSTHRESH 0x628 /* lowspeed bit threshold */ + +static inline void periodic_reinit(struct admhcd *ahcd) +{ +#if 0 + 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); +#else + u32 fit = admhc_readl(ahcd, &ahcd->regs->fminterval) & FIT; + + /* TODO: adjust FSLargestDataPacket value too? */ + admhc_writel(ahcd, (fit ^ FIT) | ahcd->fminterval, + &ahcd->regs->fminterval); +#endif +} + +static inline u32 admhc_read_rhdesc(struct admhcd *ahcd) +{ + return admhc_readl(ahcd, &ahcd->regs->rhdesc); +} + +static inline u32 admhc_read_portstatus(struct admhcd *ahcd, int port) +{ + return admhc_readl(ahcd, &ahcd->regs->portstatus[port]); +} + +static inline void admhc_write_portstatus(struct admhcd *ahcd, int port, + u32 value) +{ + admhc_writel(ahcd, value, &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) +{ + u32 t; + + t = admhc_readl(ahcd, &ahcd->regs->host_control); + if (t & ADMHC_HC_DMAE) + return; + + t |= ADMHC_HC_DMAE; + admhc_writel(ahcd, t, &ahcd->regs->host_control); + admhc_vdbg(ahcd,"DMA enabled\n"); +} + +static inline void admhc_dma_disable(struct admhcd *ahcd) +{ + u32 t; + + t = admhc_readl(ahcd, &ahcd->regs->host_control); + if (!(t & ADMHC_HC_DMAE)) + return; + + t &= ~ADMHC_HC_DMAE; + admhc_writel(ahcd, t, &ahcd->regs->host_control); + admhc_vdbg(ahcd,"DMA disabled\n"); +} |