From fe3c05491370965eb821aedc95f771b86ebab3ab Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Wed, 9 Jan 2008 02:01:44 +0300 Subject: [PATCH 45/64] Update tmio_ohci: Ports management. Basic support for ohci suspend/resume. Signed-off-by: Dmitry Baryshkov --- drivers/mfd/tc6393xb.c | 40 ++++++++ drivers/usb/host/ohci-tmio.c | 206 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 235 insertions(+), 11 deletions(-) diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c index 9439f39..5d17687 100644 --- a/drivers/mfd/tc6393xb.c +++ b/drivers/mfd/tc6393xb.c @@ -224,6 +224,44 @@ static int tc6393xb_ohci_enable(struct platform_device *ohci) return 0; } +static int tc6393xb_ohci_suspend(struct platform_device *ohci) +{ + struct platform_device *dev = to_platform_device(ohci->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + struct tc6393xb_scr __iomem *scr = tc6393xb->scr; + union tc6393xb_scr_ccr ccr; + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + ccr.raw = ioread16(&scr->ccr); + ccr.bits.usbcken = 0; + iowrite16(ccr.raw, &scr->ccr); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_ohci_resume(struct platform_device *ohci) +{ + struct platform_device *dev = to_platform_device(ohci->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + struct tc6393xb_scr __iomem *scr = tc6393xb->scr; + union tc6393xb_scr_ccr ccr; + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + ccr.raw = ioread16(&scr->ccr); + ccr.bits.usbcken = 1; + iowrite16(ccr.raw, &scr->ccr); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + static int tc6393xb_fb_disable(struct platform_device *fb) { struct platform_device *dev = to_platform_device(fb->dev.parent); @@ -423,6 +461,8 @@ static struct mfd_cell tc6393xb_cells[] = { .name = "tmio-ohci", .enable = tc6393xb_ohci_enable, .disable = tc6393xb_ohci_disable, + .suspend = tc6393xb_ohci_suspend, + .resume = tc6393xb_ohci_resume, .num_resources = ARRAY_SIZE(tc6393xb_ohci_resources), .resources = tc6393xb_ohci_resources, }, diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c index be609f3..65e3cd3 100644 --- a/drivers/usb/host/ohci-tmio.c +++ b/drivers/usb/host/ohci-tmio.c @@ -75,10 +75,13 @@ struct tmio_uhccr { u8 x07[3]; } __attribute__((packed)); +#define MAX_TMIO_OHCI_PORTS 3 + #define UHCCR_PM_GKEN 0x0001 #define UHCCR_PM_CKRNEN 0x0002 #define UHCCR_PM_USBPW1 0x0004 #define UHCCR_PM_USBPW2 0x0008 +#define UHCCR_PM_USBPW3 0x0008 #define UHCCR_PM_PMEE 0x0100 #define UHCCR_PM_PMES 0x8000 @@ -86,44 +89,96 @@ struct tmio_uhccr { struct tmio_hcd { struct tmio_uhccr __iomem *ccr; + spinlock_t lock; /* protects RMW cycles and disabled_ports data */ + bool disabled_ports[MAX_TMIO_OHCI_PORTS]; }; #define hcd_to_tmio(hcd) ((struct tmio_hcd *)(hcd_to_ohci(hcd) + 1)) #define ohci_to_tmio(ohci) ((struct tmio_hcd *)(ohci + 1)) +struct indexed_device_attribute{ + struct device_attribute dev_attr; + int index; +}; +#define to_indexed_dev_attr(_dev_attr) \ + container_of(_dev_attr, struct indexed_device_attribute, dev_attr) + +#define INDEXED_ATTR(_name, _mode, _show, _store, _index) \ + { .dev_attr = __ATTR(_name ## _index, _mode, _show, _store), \ + .index = _index } + +#define INDEXED_DEVICE_ATTR(_name, _mode, _show, _store, _index) \ +struct indexed_device_attribute dev_attr_##_name ## _index \ + = INDEXED_ATTR(_name, _mode, _show, _store, _index) + +static bool disabled_tmio_ports[MAX_TMIO_OHCI_PORTS]; +module_param_array(disabled_tmio_ports, bool, NULL, 0644); +MODULE_PARM_DESC(disabled_tmio_ports, + "disable specified TC6393 usb ports (default: all enabled)"); + /*-------------------------------------------------------------------------*/ +static void tmio_write_pm(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + struct tmio_uhccr __iomem *ccr = tmio->ccr; + u16 pm; + unsigned long flags; + + spin_lock_irqsave(&tmio->lock, flags); + + pm = UHCCR_PM_GKEN | UHCCR_PM_CKRNEN | + UHCCR_PM_PMEE | UHCCR_PM_PMES; + + if (tmio->disabled_ports[0]) + pm |= UHCCR_PM_USBPW1; + if (tmio->disabled_ports[1]) + pm |= UHCCR_PM_USBPW2; + if (tmio->disabled_ports[2]) + pm |= UHCCR_PM_USBPW3; + + iowrite16(pm, &ccr->pm); + spin_unlock_irqrestore(&tmio->lock, flags); +} + static void tmio_stop_hc(struct platform_device *dev) { struct mfd_cell *cell = mfd_get_cell(dev); struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); struct tmio_hcd *tmio = hcd_to_tmio(hcd); struct tmio_uhccr __iomem *ccr = tmio->ccr; u16 pm; - pm = UHCCR_PM_GKEN | UHCCR_PM_CKRNEN | UHCCR_PM_USBPW1 | UHCCR_PM_USBPW2; + pm = UHCCR_PM_GKEN | UHCCR_PM_CKRNEN; + switch (ohci->num_ports) { + default: + dev_err(&dev->dev, "Unsupported amount of ports: %d\n", ohci->num_ports); + case 3: + pm |= UHCCR_PM_USBPW3; + case 2: + pm |= UHCCR_PM_USBPW2; + case 1: + pm |= UHCCR_PM_USBPW1; + } iowrite8(0, &ccr->intc); iowrite8(0, &ccr->ilme); iowrite16(0, &ccr->basel); iowrite16(0, &ccr->baseh); - iowrite16(pm, &ccr->pm); + iowrite16(pm, &ccr->pm); cell->disable(dev); } static void tmio_start_hc(struct platform_device *dev) { - struct mfd_cell *cell = mfd_get_cell(dev); struct usb_hcd *hcd = platform_get_drvdata(dev); struct tmio_hcd *tmio = hcd_to_tmio(hcd); struct tmio_uhccr __iomem *ccr = tmio->ccr; - u16 pm; unsigned long base = hcd->rsrc_start; - pm = UHCCR_PM_CKRNEN | UHCCR_PM_GKEN | UHCCR_PM_PMEE | UHCCR_PM_PMES; - cell->enable(dev); - - iowrite16(pm, &ccr->pm); + tmio_write_pm(dev); iowrite16(base, &ccr->basel); iowrite16(base >> 16, &ccr->baseh); iowrite8(1, &ccr->ilme); @@ -133,9 +188,56 @@ static void tmio_start_hc(struct platform_device *dev) ioread8(&ccr->revid), hcd->rsrc_start, hcd->irq); } +static ssize_t tmio_disabled_port_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + int index = to_indexed_dev_attr(attr)->index; + return snprintf(buf, PAGE_SIZE, "%c", + tmio->disabled_ports[index-1]? 'Y': 'N'); +} + +static ssize_t tmio_disabled_port_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + int index = to_indexed_dev_attr(attr)->index; + + if (!count) + return -EINVAL; + + switch (buf[0]) { + case 'y': case 'Y': case '1': + tmio->disabled_ports[index-1] = true; + break; + case 'n': case 'N': case '0': + tmio->disabled_ports[index-1] = false; + break; + default: + return -EINVAL; + } + + tmio_write_pm(to_platform_device(dev)); + + return 1; +} + + +static INDEXED_DEVICE_ATTR(disabled_usb_port, S_IRUGO | S_IWUSR, + tmio_disabled_port_show, tmio_disabled_port_store, 1); +static INDEXED_DEVICE_ATTR(disabled_usb_port, S_IRUGO | S_IWUSR, + tmio_disabled_port_show, tmio_disabled_port_store, 2); +static INDEXED_DEVICE_ATTR(disabled_usb_port, S_IRUGO | S_IWUSR, + tmio_disabled_port_show, tmio_disabled_port_store, 3); + static int usb_hcd_tmio_probe(const struct hc_driver *driver, struct platform_device *dev) { + struct mfd_cell *cell = mfd_get_cell(dev); struct resource *config = platform_get_resource_byname(dev, IORESOURCE_MEM, TMIO_OHCI_CONFIG); struct resource *regs = platform_get_resource_byname(dev, IORESOURCE_MEM, TMIO_OHCI_CONTROL); struct resource *sram = platform_get_resource_byname(dev, IORESOURCE_MEM, TMIO_OHCI_SRAM); @@ -159,6 +261,12 @@ static int usb_hcd_tmio_probe(const struct hc_driver *driver, tmio = hcd_to_tmio(hcd); + spin_lock_init(&tmio->lock); + + memcpy(tmio->disabled_ports, + disabled_tmio_ports, + sizeof(disabled_tmio_ports)); + tmio->ccr = ioremap(config->start, config->end - config->start + 1); if (!tmio->ccr) { retval = -ENOMEM; @@ -183,17 +291,46 @@ static int usb_hcd_tmio_probe(const struct hc_driver *driver, if (retval) goto err_dmabounce_register_dev; + retval = cell->enable(dev); + if (retval) + goto err_enable; + tmio_start_hc(dev); ohci = hcd_to_ohci(hcd); ohci_hcd_init(ohci); retval = usb_add_hcd(hcd, irq, IRQF_DISABLED); + if (retval) + goto err_add_hcd; + + switch (ohci->num_ports) { + default: + dev_err(&dev->dev, "Unsupported amount of ports: %d\n", + ohci->num_ports); + case 3: + retval |= device_create_file(&dev->dev, + &dev_attr_disabled_usb_port3.dev_attr); + case 2: + retval |= device_create_file(&dev->dev, + &dev_attr_disabled_usb_port2.dev_attr); + case 1: + retval |= device_create_file(&dev->dev, + &dev_attr_disabled_usb_port1.dev_attr); + } if (retval == 0) return retval; - tmio_stop_hc(dev); + device_remove_file(&dev->dev, &dev_attr_disabled_usb_port3.dev_attr); + device_remove_file(&dev->dev, &dev_attr_disabled_usb_port2.dev_attr); + device_remove_file(&dev->dev, &dev_attr_disabled_usb_port1.dev_attr); + + usb_remove_hcd(hcd); +err_add_hcd: + tmio_stop_hc(dev); + cell->disable(dev); +err_enable: dmabounce_unregister_dev(&dev->dev); err_dmabounce_register_dev: dma_release_declared_memory(&dev->dev); @@ -212,6 +349,9 @@ static void usb_hcd_tmio_remove(struct usb_hcd *hcd, struct platform_device *dev { struct tmio_hcd *tmio = hcd_to_tmio(hcd); + device_remove_file(&dev->dev, &dev_attr_disabled_usb_port3.dev_attr); + device_remove_file(&dev->dev, &dev_attr_disabled_usb_port2.dev_attr); + device_remove_file(&dev->dev, &dev_attr_disabled_usb_port1.dev_attr); usb_remove_hcd(hcd); tmio_stop_hc(dev); dmabounce_unregister_dev(&dev->dev); @@ -297,13 +437,22 @@ static u64 dma_mask = DMA_32BIT_MASK; static int ohci_hcd_tmio_drv_probe(struct platform_device *dev) { struct resource *sram = platform_get_resource_byname(dev, IORESOURCE_MEM, TMIO_OHCI_SRAM); + int retval; dev->dev.dma_mask = &dma_mask; dev->dev.coherent_dma_mask = DMA_32BIT_MASK; + /* FIXME: move dmabounce checkers to tc6393xb core? */ dmabounce_register_checker(tmio_dmabounce_check, sram); - return usb_hcd_tmio_probe(&ohci_tmio_hc_driver, dev); + retval = usb_hcd_tmio_probe(&ohci_tmio_hc_driver, dev); + + if (retval == 0) + return retval; + + dmabounce_remove_checker(tmio_dmabounce_check, sram); + + return retval; } static int ohci_hcd_tmio_drv_remove(struct platform_device *dev) @@ -323,14 +472,31 @@ static int ohci_hcd_tmio_drv_remove(struct platform_device *dev) #ifdef CONFIG_PM static int ohci_hcd_tmio_drv_suspend(struct platform_device *dev, pm_message_t state) { + struct mfd_cell *cell = mfd_get_cell(dev); struct usb_hcd *hcd = platform_get_drvdata(dev); struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + struct tmio_uhccr __iomem *ccr = tmio->ccr; + unsigned long flags; + u8 misc; + int ret; if (time_before(jiffies, ohci->next_statechange)) msleep(5); ohci->next_statechange = jiffies; - tmio_stop_hc(dev); + spin_lock_irqsave(&tmio->lock, flags); + + misc = ioread8(&ccr->misc); + misc |= 1 << 3; /* USSUSP */ + iowrite8(misc, &ccr->misc); + + spin_unlock_irqrestore(&tmio->lock, flags); + + ret = cell->suspend(dev); + if (ret) + return ret; + hcd->state = HC_STATE_SUSPENDED; dev->dev.power.power_state = PMSG_SUSPEND; @@ -339,15 +505,33 @@ static int ohci_hcd_tmio_drv_suspend(struct platform_device *dev, pm_message_t s static int ohci_hcd_tmio_drv_resume(struct platform_device *dev) { + struct mfd_cell *cell = mfd_get_cell(dev); struct usb_hcd *hcd = platform_get_drvdata(dev); struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + struct tmio_uhccr __iomem *ccr = tmio->ccr; + unsigned long flags; + u8 misc; + int ret; if (time_before(jiffies, ohci->next_statechange)) msleep(5); ohci->next_statechange = jiffies; + ret = cell->resume(dev); + if (ret) + return ret; + tmio_start_hc(dev); + spin_lock_irqsave(&tmio->lock, flags); + + misc = ioread8(&ccr->misc); + misc &= ~(1 << 3); /* USSUSP */ + iowrite8(misc, &ccr->misc); + + spin_unlock_irqrestore(&tmio->lock, flags); + dev->dev.power.power_state = PMSG_ON; usb_hcd_resume_root_hub(hcd); -- 1.5.3.8