X-Git-Url: https://git.rohieb.name/openwrt.git/blobdiff_plain/5e9852b169f255fa1094d08f470553035edf447a..20deea5f7c4cfd0b82451c08a1c2d53860bde3e8:/target/linux/adm5120-2.6/files/drivers/usb/host/adm5120-hcd.c diff --git a/target/linux/adm5120-2.6/files/drivers/usb/host/adm5120-hcd.c b/target/linux/adm5120-2.6/files/drivers/usb/host/adm5120-hcd.c index 6f0df78c3..875dc6c0b 100644 --- a/target/linux/adm5120-2.6/files/drivers/usb/host/adm5120-hcd.c +++ b/target/linux/adm5120-2.6/files/drivers/usb/host/adm5120-hcd.c @@ -3,7 +3,7 @@ * * Copyright (C) 2005 Jeroen Vreeken (pe1rxq@amsat.org) * - * Based on the ADMtek 2.4 driver + * Based on the ADMtek 2.4 driver * (C) Copyright 2003 Junius Chen * Which again was based on the ohci and uhci drivers. */ @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -30,53 +31,67 @@ MODULE_DESCRIPTION("ADM5120 USB Host Controller Driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jeroen Vreeken (pe1rxq@amsat.org)"); +#define PFX "adm5120-hcd: " + #define ADMHCD_REG_CONTROL 0x00 +#define ADMHCD_SW_RESET 0x00000008 /* Reset */ +#define ADMHCD_DMAA 0x00000004 /* DMA arbitration control */ +#define ADMHCD_SW_INTREQ 0x00000002 /* request software int */ +#define ADMHCD_HOST_EN 0x00000001 /* Host enable */ #define ADMHCD_REG_INTSTATUS 0x04 +#define ADMHCD_INT_ACT 0x80000000 /* Interrupt active */ +#define ADMHCD_INT_FATAL 0x40000000 /* Fatal interrupt */ +#define ADMHCD_INT_SW 0x20000000 /* software interrupt */ +#define ADMHCD_INT_TD 0x00100000 /* TD completed */ +#define ADMHCD_FNO 0x00000800 /* Frame number overaflow */ +#define ADMHCD_SO 0x00000400 /* Scheduling overrun */ +#define ADMHCD_INSMI 0x00000200 /* Root hub status change */ +#define ADMHCD_BABI 0x00000100 /* Babble detected, host mode */ +#define ADMHCD_RESI 0x00000020 /* Resume detected */ +#define ADMHCD_SOFI 0x00000010 /* SOF transmitted/received, host mode */ #define ADMHCD_REG_INTENABLE 0x08 +#define ADMHCD_INT_EN 0x80000000 /* Interrupt enable */ +#define ADMHCD_INTMASK 0x00000001 /* Interrupt mask */ #define ADMHCD_REG_HOSTCONTROL 0x10 +#define ADMHCD_DMA_EN 0x00000004 /* USB host DMA enable */ +#define ADMHCD_STATE_MASK 0x00000003 +#define ADMHCD_STATE_RST 0x00000000 /* bus state reset */ +#define ADMHCD_STATE_RES 0x00000001 /* bus state resume */ +#define ADMHCD_STATE_OP 0x00000002 /* bus state operational */ +#define ADMHCD_STATE_SUS 0x00000003 /* bus state suspended */ #define ADMHCD_REG_FMINTERVAL 0x18 #define ADMHCD_REG_FMNUMBER 0x1c #define ADMHCD_REG_LSTHRESH 0x70 #define ADMHCD_REG_RHDESCR 0x74 +#define ADMHCD_CRWE 0x20000000 /* Clear wakeup enable */ +#define ADMHCD_DRWE 0x10000000 /* Device remote wakeup enable */ +#define ADMHCD_HW_OCIC 0x08000000 /* Over current indication change */ +#define ADMHCD_LPSC 0x04000000 /* Local power switch change */ +#define ADMHCD_OCI 0x02000000 /* Over current indication */ +#define ADMHCD_LPS 0x01000000 /* Local power switch/global power switch */ +#define ADMHCD_NOCP 0x00000800 /* No over current protect mode */ +#define ADMHCD_OPCM 0x00000400 /* Over current protect mode */ +#define ADMHCD_NPS 0x00000200 /* No Power Switch */ +#define ADMHCD_PSM 0x00000100 /* Power switch mode */ #define ADMHCD_REG_PORTSTATUS0 0x78 +#define ADMHCD_CCS 0x00000001 /* current connect status */ +#define ADMHCD_PES 0x00000002 /* port enable status */ +#define ADMHCD_PSS 0x00000004 /* port suspend status */ +#define ADMHCD_POCI 0x00000008 /* port overcurrent indicator */ +#define ADMHCD_PRS 0x00000010 /* port reset status */ +#define ADMHCD_PPS 0x00000100 /* port power status */ +#define ADMHCD_LSDA 0x00000200 /* low speed device attached */ +#define ADMHCD_CSC 0x00010000 /* connect status change */ +#define ADMHCD_PESC 0x00020000 /* enable status change */ +#define ADMHCD_PSSC 0x00040000 /* suspend status change */ +#define ADMHCD_OCIC 0x00080000 /* overcurrent change*/ +#define ADMHCD_PRSC 0x00100000 /* reset status change */ #define ADMHCD_REG_PORTSTATUS1 0x7c #define ADMHCD_REG_HOSTHEAD 0x80 #define ADMHCD_NUMPORTS 2 -#define ADMHCD_HOST_EN 0x00000001 /* Host enable */ -#define ADMHCD_SW_INTREQ 0x00000002 /* request software int */ -#define ADMHCD_SW_RESET 0x00000008 /* Reset */ - -#define ADMHCD_INT_TD 0x00100000 /* TD completed */ -#define ADMHCD_INT_SW 0x20000000 /* software interrupt */ -#define ADMHCD_INT_FATAL 0x40000000 /* Fatal interrupt */ -#define ADMHCD_INT_ACT 0x80000000 /* Interrupt active */ - -#define ADMHCD_STATE_RST 0x00000000 /* bus state reset */ -#define ADMHCD_STATE_RES 0x00000001 /* bus state resume */ -#define ADMHCD_STATE_OP 0x00000002 /* bus state operational */ -#define ADMHCD_STATE_SUS 0x00000003 /* bus state suspended */ -#define ADMHCD_DMA_EN 0x00000004 /* enable dma engine */ - -#define ADMHCD_NPS 0x00000020 /* No Power Switch */ -#define ADMHCD_LPSC 0x04000000 /* Local power switch change */ - -#define ADMHCD_CCS 0x00000001 /* current connect status */ -#define ADMHCD_PES 0x00000002 /* port enable status */ -#define ADMHCD_PSS 0x00000004 /* port suspend status */ -#define ADMHCD_POCI 0x00000008 /* port overcurrent indicator */ -#define ADMHCD_PRS 0x00000010 /* port reset status */ -#define ADMHCD_PPS 0x00000100 /* port power status */ -#define ADMHCD_LSDA 0x00000200 /* low speed device attached */ -#define ADMHCD_CSC 0x00010000 /* connect status change */ -#define ADMHCD_PESC 0x00020000 /* enable status change */ -#define ADMHCD_PSSC 0x00040000 /* suspend status change */ -#define ADMHCD_OCIC 0x00080000 /* overcurrent change*/ -#define ADMHCD_PRSC 0x00100000 /* reset status change */ - - struct admhcd_ed { /* Don't change first four, they used for DMA */ u32 control; @@ -146,8 +161,7 @@ static int admhcd_td_err[16] = { struct admhcd { spinlock_t lock; - - void __iomem *addr_reg; + void __iomem *data_reg; /* Root hub registers */ u32 rhdesca; @@ -160,7 +174,7 @@ struct admhcd { u32 base; u32 dma_en; unsigned long flags; - + }; static inline struct admhcd *hcd_to_admhcd(struct usb_hcd *hcd) @@ -213,7 +227,7 @@ static struct admhcd_td *admhcd_td_alloc(struct admhcd_ed *ed, struct urb *urb) if (ed->cur == NULL) { ed->cur = tdn; ed->head = tdn; - ed->tail = tdn; + ed->tail = tdn; td = tdn; } else { /* Supply back the old tail and link in new td as tail */ @@ -328,9 +342,8 @@ static void admhcd_ed_start(struct admhcd *ahcd, struct admhcd_ed *ed) ahcd->dma_en |= ADMHCD_DMA_EN; } -static irqreturn_t adm5120hcd_irq(int irq, void *ptr, struct pt_regs *regs) +static irqreturn_t adm5120hcd_irq(struct usb_hcd *hcd) { - struct usb_hcd *hcd = (struct usb_hcd *)ptr; struct admhcd *ahcd = hcd_to_admhcd(hcd); u32 intstatus; @@ -345,7 +358,7 @@ static irqreturn_t adm5120hcd_irq(int irq, void *ptr, struct pt_regs *regs) } if (intstatus & ADMHCD_INT_TD) { struct admhcd_ed *ed, *head; - + admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_TD); head = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD); @@ -355,7 +368,7 @@ static irqreturn_t adm5120hcd_irq(int irq, void *ptr, struct pt_regs *regs) if (ed->urb && !(ed->cur->control & ADMHCD_TD_OWN)) { struct admhcd_td *td; int error; - + td = ed->cur; error = (td->control & ADMHCD_TD_ERRMASK) >> ADMHCD_TD_ERRSHIFT; @@ -376,7 +389,7 @@ static irqreturn_t adm5120hcd_irq(int irq, void *ptr, struct pt_regs *regs) } static int admhcd_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep, - struct urb *urb, int mem_flags) + struct urb *urb, gfp_t mem_flags) { struct admhcd *ahcd = hcd_to_admhcd(hcd); struct admhcd_ed *ed; @@ -442,7 +455,7 @@ static int admhcd_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep, td = admhcd_td_fill(ADMHCD_TD_SETUP | ADMHCD_TD_DATA0, td, (dma_addr_t)urb->setup_packet, 8); while (data_len > 0) { - td = admhcd_td_fill(ADMHCD_TD_DATA1 + td = admhcd_td_fill(ADMHCD_TD_DATA1 | ADMHCD_TD_R | (usb_pipeout(pipe) ? ADMHCD_TD_OUT : ADMHCD_TD_IN), td, @@ -459,7 +472,7 @@ static int admhcd_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep, i = 0; while(data_len > 4096) { td = admhcd_td_fill((usb_pipeout(pipe) ? - ADMHCD_TD_OUT : + ADMHCD_TD_OUT : ADMHCD_TD_IN | ADMHCD_TD_R) | (i ? ADMHCD_TD_TOGGLE : toggle), td, data, 4096); @@ -467,7 +480,7 @@ static int admhcd_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep, data_len -= 4096; i++; } - td = admhcd_td_fill((usb_pipeout(pipe) ? + td = admhcd_td_fill((usb_pipeout(pipe) ? ADMHCD_TD_OUT : ADMHCD_TD_IN) | (i ? ADMHCD_TD_TOGGLE : toggle), td, data, data_len); i++; @@ -616,7 +629,7 @@ static int admhcd_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_RESET: if (admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4) & ADMHCD_CCS) { - admhcd_reg_set(ahcd, + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, ADMHCD_PRS | ADMHCD_CSC); mdelay(50); @@ -685,10 +698,139 @@ err: return retval; } +static int admhcd_start(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + unsigned long flags; + + printk(KERN_DEBUG PFX "calling admhcd_start\n"); + + spin_lock_irqsave(&ahcd->lock, flags); + + /* Initialise the HCD registers */ + admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0); + mdelay(10); + + admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_SW_RESET); + + while (admhcd_reg_get(ahcd, ADMHCD_REG_CONTROL) & ADMHCD_SW_RESET) { + printk(KERN_WARNING PFX "waiting for reset to complete\n"); + mdelay(1); + } + + hcd->uses_new_polling = 1; + + /* Enable USB host mode */ + admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_HOST_EN); + + /* Set host specific settings */ + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, 0x00000000); + admhcd_reg_set(ahcd, ADMHCD_REG_FMINTERVAL, 0x20002edf); + admhcd_reg_set(ahcd, ADMHCD_REG_LSTHRESH, 0x628); + + /* Set interrupts */ + admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, + ADMHCD_INT_ACT | ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD); + admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, + ADMHCD_INT_ACT | ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD); + + /* Power on all ports */ + admhcd_reg_set(ahcd, ADMHCD_REG_RHDESCR, ADMHCD_NPS | ADMHCD_LPSC); + + /* HCD is now operationnal */ + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, ADMHCD_STATE_OP); + + hcd->state = HC_STATE_RUNNING; + + spin_unlock_irqrestore(&ahcd->lock, flags); + + printk(KERN_DEBUG PFX "returning 0 from admhcd_start\n"); + return 0; +} + +static int admhcd_sw_reset(struct admhcd *ahcd) +{ + int retries = 15; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ahcd->lock, flags); + + admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0); + mdelay(10); + + admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_SW_RESET); + + while (--retries) { + mdelay(1); + if (!(admhcd_reg_get(ahcd, ADMHCD_REG_CONTROL) & ADMHCD_SW_RESET)) + break; + } + if (!retries) { + printk(KERN_WARNING "%s: software reset timeout\n", hcd_name); + ret = -ETIME; + } + spin_unlock_irqrestore(&ahcd->lock, flags); + return ret; +} + +static int admhcd_reset(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + u32 state = 0; + int ret, timeout = 15; /* ms */ + unsigned long t; + + ret = admhcd_sw_reset(ahcd); + if (ret) + return ret; + + t = jiffies + msecs_to_jiffies(timeout); + do { + spin_lock_irq(&ahcd->lock); + state = admhcd_reg_get(ahcd, ADMHCD_REG_HOSTCONTROL); + spin_unlock_irq(&ahcd->lock); + state &= ADMHCD_STATE_MASK; + if (state == ADMHCD_STATE_RST) + break; + msleep(4); + } while (time_before_eq(jiffies, t)); + + if (state != ADMHCD_STATE_RST) { + printk(KERN_WARNING "%s: device not ready after %dms\n", + hcd_name, timeout); + ret = -ENODEV; + } + + return ret; +} + +static void admhcd_stop(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&ahcd->lock, flags); + admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0); + + /* Set global control of power for ports */ + val = admhcd_reg_get(ahcd, ADMHCD_REG_RHDESCR); + val &= (~ADMHCD_PSM | ADMHCD_LPS); + admhcd_reg_set(ahcd, ADMHCD_REG_RHDESCR, val); + + spin_unlock_irqrestore(&ahcd->lock, flags); + + /* Ask for software reset */ + admhcd_sw_reset(ahcd); +} + + static struct hc_driver adm5120_hc_driver = { .description = hcd_name, .product_desc = "ADM5120 HCD", .hcd_priv_size = sizeof(struct admhcd), + .irq = adm5120hcd_irq, .flags = HCD_USB11, .urb_enqueue = admhcd_urb_enqueue, .urb_dequeue = admhcd_urb_dequeue, @@ -696,113 +838,116 @@ static struct hc_driver adm5120_hc_driver = { .get_frame_number = admhcd_get_frame_number, .hub_status_data = admhcd_hub_status_data, .hub_control = admhcd_hub_control, + .start = admhcd_start, + .stop = admhcd_stop, + .reset = admhcd_reset, }; #define resource_len(r) (((r)->end - (r)->start) + 1) static int __init adm5120hcd_probe(struct platform_device *pdev) { - struct usb_hcd *hcd; - struct admhcd *ahcd; - struct usb_device *udev; - struct resource *addr, *data; - void __iomem *addr_reg; - void __iomem *data_reg; - int irq, err = 0; + struct usb_hcd *hcd; + struct admhcd *ahcd; + struct resource *data; + void __iomem *data_reg; - if (pdev->num_resources < 3) { + int err = 0, irq; + + if (pdev->num_resources < 2) { + printk(KERN_WARNING PFX "not enough resources\n"); err = -ENODEV; goto out; - } + } - data = platform_get_resource(pdev, IORESOURCE_MEM, 0); - addr = platform_get_resource(pdev, IORESOURCE_MEM, 1); irq = platform_get_irq(pdev, 0); - - if (!addr || !data || irq < 0) { - err = -ENODEV; - goto out; - } + data = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (pdev->dev.dma_mask) { - printk(KERN_DEBUG "DMA not supported\n"); - err = -EINVAL; - goto out; - } + printk(KERN_DEBUG PFX "no we won't dma\n"); + return -EINVAL; + } + + if (!data || irq < 0) { + printk(KERN_DEBUG PFX "either IRQ or data resource is invalid\n"); + err = -ENODEV; + goto out; + } + + if (!request_mem_region(data->start, resource_len(data), hcd_name)) { + printk(KERN_DEBUG PFX "cannot request memory regions for the data resource\n"); + err = -EBUSY; + goto out; + } + + data_reg = ioremap(data->start, resource_len(data)); + if (data_reg == NULL) { + printk(KERN_DEBUG PFX "unable to ioremap\n"); + err = -ENOMEM; + goto out_mem; + } - if (!request_mem_region(addr->start, 2, hcd_name)) { - err = -EBUSY; - goto out; - } - addr_reg = ioremap(addr->start, resource_len(addr)); - if (addr_reg == NULL) { - err = -ENOMEM; - goto out_mem; - } - if (!request_mem_region(data->start, 2, hcd_name)) { - err = -EBUSY; - goto out_unmap; - } - data_reg = ioremap(data->start, resource_len(data)); - if (data_reg == NULL) { + hcd = usb_create_hcd(&adm5120_hc_driver, &pdev->dev, pdev->dev.bus_id); + if (!hcd) { + printk(KERN_DEBUG PFX "unable to create the hcd\n"); err = -ENOMEM; - goto out_mem; + goto out_unmap; } - - hcd = usb_create_hcd(&adm5120_hc_driver, &pdev->dev, pdev->dev.bus_id); - if (!hcd) - goto out_mem; + hcd->rsrc_start = data->start; + hcd->rsrc_len = resource_len(data); + hcd->regs = data_reg; ahcd = hcd_to_admhcd(hcd); ahcd->data_reg = data_reg; - ahcd->addr_reg = addr_reg; + ahcd->base = (u32)data_reg; + spin_lock_init(&ahcd->lock); INIT_LIST_HEAD(&ahcd->async); - /* Initialise the HCD registers */ - admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0); - mdelay(10); - admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_SW_RESET); - while (admhcd_reg_get(ahcd, ADMHCD_REG_CONTROL) & ADMHCD_SW_RESET) - mdelay(1); - - admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_HOST_EN); - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, 0x00000000); - admhcd_reg_set(ahcd, ADMHCD_REG_FMINTERVAL, 0x20002edf); - admhcd_reg_set(ahcd, ADMHCD_REG_LSTHRESH, 0x628); - admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, - ADMHCD_INT_ACT | ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD); - admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, - ADMHCD_INT_ACT | ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD); - admhcd_reg_set(ahcd, ADMHCD_REG_RHDESCR, ADMHCD_NPS | ADMHCD_LPSC); - admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, ADMHCD_STATE_OP); + hcd->product_desc = "ADM5120 HCD"; err = usb_add_hcd(hcd, irq, IRQF_DISABLED); - if (err) + if (err) { + printk(KERN_DEBUG PFX "unable to add hcd\n"); goto out_dev; + } return 0; out_dev: usb_put_hcd(hcd); -out_irq: - free_irq(pdev->resource[1].start, hcd); out_unmap: - iounmap(addr_reg); -out_hcd: - usb_put_hcd(hcd); + iounmap(data_reg); out_mem: - release_mem_region(addr->start, 2); + release_mem_region(pdev->resource[0].start, pdev->resource[0].end - pdev->resource[0].start +1); out: return err; } +#ifdef CONFIG_PM +static int adm5120hcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + pdev-dev.power.power_state = state; + mdelay(1); + return 0; +} + +static int adm5120hcd_resume(struct platform_device *pdev, pm_message_t state) +{ + pdev->dev.power.power_state = PMSG_ON; + mdelay(1); + return 0; +} +#else +#define adm5120hcd_suspend NULL +#define adm5120hcd_resume NULL +#endif + static int __init_or_module adm5120hcd_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct admhcd *ahcd; - struct resource *res; if (!hcd) return 0; @@ -816,25 +961,39 @@ static int __init_or_module adm5120hcd_remove(struct platform_device *pdev) static struct platform_driver adm5120hcd_driver = { .probe = adm5120hcd_probe, .remove = adm5120hcd_remove, + .suspend = adm5120hcd_suspend, + .remove = adm5120hcd_resume, .driver = { - .name = "adm5120-hcd", + .name = (char *)hcd_name, .owner = THIS_MODULE, }, }; static int __init adm5120hcd_init(void) { - if (usb_disabled()) + int ret; + + if (usb_disabled()) { + printk(KERN_DEBUG PFX "USB support is disabled\n"); return -ENODEV; - if (!adm5120_info.has_usb) + } + + if (mips_machgroup != MACH_GROUP_ADM5120) { + printk(KERN_DEBUG PFX "unsupported machine group\n"); return -ENODEV; + } + + ret = platform_driver_register(&adm5120hcd_driver); + if (ret == 0) + printk(KERN_INFO PFX "registered\n"); - return platform_driver_register(&adm5120hcd_driver); + return ret; } static void __exit adm5120hcd_exit(void) { platform_driver_unregister(&adm5120hcd_driver); + printk(KERN_INFO PFX "driver unregistered\n"); } module_init(adm5120hcd_init);