From patchwork Tue Jan 12 22:56:22 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthew Garrett X-Patchwork-Id: 72458 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.3/8.14.2) with ESMTP id o0CMvcLM028068 for ; Tue, 12 Jan 2010 22:57:39 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753028Ab0ALW5i (ORCPT ); Tue, 12 Jan 2010 17:57:38 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1753897Ab0ALW5i (ORCPT ); Tue, 12 Jan 2010 17:57:38 -0500 Received: from cavan.codon.org.uk ([93.93.128.6]:33405 "EHLO cavan.codon.org.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753028Ab0ALW5h (ORCPT ); Tue, 12 Jan 2010 17:57:37 -0500 Received: from lan-nat-pool-bos.redhat.com ([66.187.234.200] helo=localhost.localdomain) by cavan.codon.org.uk with esmtpsa (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.69) (envelope-from ) id 1NUpfr-0001hu-RX; Tue, 12 Jan 2010 22:57:20 +0000 From: Matthew Garrett To: linux-pm@lists.linux-foundation.org Cc: rjw@sisk.pl, linux1394-devel@lists.sourceforge.net, linux-acpi@vger.kernel.org, Matthew Garrett Subject: [PATCH] [RFC] firewire: Add ohci1394 PCI runtime power management support Date: Tue, 12 Jan 2010 17:56:22 -0500 Message-Id: <1263336982-15424-1-git-send-email-mjg@redhat.com> X-Mailer: git-send-email 1.6.5.2 X-SA-Do-Not-Run: Yes X-SA-Exim-Connect-IP: 66.187.234.200 X-SA-Exim-Mail-From: mjg@redhat.com X-SA-Exim-Scanned: No (on cavan.codon.org.uk); SAEximRunCond expanded to false Sender: linux-acpi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-acpi@vger.kernel.org diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index a61571c..3f90de9 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -184,6 +185,7 @@ struct fw_ohci { dma_addr_t self_id_bus; __le32 *self_id_cpu; struct tasklet_struct bus_reset_tasklet; + struct tasklet_struct phy_tasklet; int node_id; int generation; int request_generation; /* for timestamping incoming requests */ @@ -217,6 +219,8 @@ struct fw_ohci { u64 ir_context_channels; u32 ir_context_mask; struct iso_context *ir_context_list; + + bool runtime_pm; }; static inline struct fw_ohci *fw_ohci(struct fw_card *card) @@ -452,25 +456,81 @@ static inline void flush_writes(const struct fw_ohci *ohci) reg_read(ohci, OHCI1394_Version); } -static int ohci_update_phy_reg(struct fw_card *card, int addr, - int clear_bits, int set_bits) +static int ohci_read_phy_reg(struct fw_card *card, int addr, u32 *val) { struct fw_ohci *ohci = fw_ohci(card); - u32 val, old; + int i; reg_write(ohci, OHCI1394_PhyControl, OHCI1394_PhyControl_Read(addr)); flush_writes(ohci); - msleep(2); - val = reg_read(ohci, OHCI1394_PhyControl); - if ((val & OHCI1394_PhyControl_ReadDone) == 0) { - fw_error("failed to set phy reg bits.\n"); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + *val = reg_read(ohci, OHCI1394_PhyControl); + if (*val & OHCI1394_PhyControl_ReadDone) + break; + mdelay(1); + } + + if (i == OHCI_LOOP_COUNT) { + fw_error("Get PHY Reg timeout [0x%08x/0x%08x/%d]", + *val, *val & OHCI1394_PhyControl_ReadDone, i); return -EBUSY; } - old = OHCI1394_PhyControl_ReadData(val); - old = (old & ~clear_bits) | set_bits; + *val = OHCI1394_PhyControl_ReadData(*val); + + return 0; +} + +static int ohci_update_phy_reg(struct fw_card *card, int addr, + int clear_bits, int set_bits) +{ + struct fw_ohci *ohci = fw_ohci(card); + u32 r; + int i; + + if (ohci_read_phy_reg(card, addr, &r)) + return -EBUSY; + + r = (r & ~clear_bits) | set_bits; reg_write(ohci, OHCI1394_PhyControl, - OHCI1394_PhyControl_Write(addr, old)); + OHCI1394_PhyControl_Write(addr, r)); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + r = reg_read(ohci, OHCI1394_PhyControl); + if (!(r & OHCI1394_PhyControl_WriteDone)) + break; + } + + if (i == OHCI_LOOP_COUNT) { + fw_error("Set PHY Reg timeout [0x%08x/0x%08x/%d]", + r, r & OHCI1394_PhyControl_WriteDone, i); + return -EBUSY; + } + + return 0; +} + +static int ohci_read_port_reg(struct fw_card *card, int port, + int addr, u32 *val) +{ + if (ohci_update_phy_reg(card, 7, 0xff, port)) + return -EBUSY; + + if (ohci_read_phy_reg(card, 8+addr, val)) + return -EBUSY; + + return 0; +} + +static int ohci_write_port_reg(struct fw_card *card, int port, + int addr, int clear_bits, int set_bits) +{ + if (ohci_update_phy_reg(card, 7, 0xff, port)) + return -EBUSY; + + if (ohci_update_phy_reg(card, 8+addr, clear_bits, set_bits)) + return -EBUSY; return 0; } @@ -1250,6 +1310,45 @@ static void at_context_transmit(struct context *ctx, struct fw_packet *packet) } +static int ohci_port_is_connected(struct fw_card *card) +{ + int i, total_ports; + u32 val; + + if (ohci_read_phy_reg(card, 2, &val)) + return -EBUSY; + + total_ports = val & 0x1f; + + for (i = 0; i < total_ports; i++) { + ohci_read_port_reg(card, i, 0, &val); + if (val & 0x04) + return 1; + } + + return 0; +} + +static int ohci_clear_port_event(struct fw_card *card) +{ + + return ohci_update_phy_reg(card, 5, 0, 0x04); +} + +static void phy_tasklet(unsigned long data) +{ + struct fw_ohci *ohci = (struct fw_ohci *)data; + u32 val; + + ohci_read_phy_reg(&ohci->card, 5, &val); + + if (val & 0x04) + ohci_clear_port_event(&ohci->card); + + if (!ohci_port_is_connected(&ohci->card)) + pm_schedule_suspend(ohci->card.device, 1000); +} + static void bus_reset_tasklet(unsigned long data) { struct fw_ohci *ohci = (struct fw_ohci *)data; @@ -1259,14 +1358,17 @@ static void bus_reset_tasklet(unsigned long data) void *free_rom = NULL; dma_addr_t free_rom_bus = 0; + /* Cancel any pending suspend requests */ + pm_runtime_resume(ohci->card.device); + reg = reg_read(ohci, OHCI1394_NodeID); if (!(reg & OHCI1394_NodeID_idValid)) { fw_notify("node ID not valid, new bus reset in progress\n"); - return; + goto out; } if ((reg & OHCI1394_NodeID_nodeNumber) == 63) { fw_notify("malconfigured bus\n"); - return; + goto out; } ohci->node_id = reg & (OHCI1394_NodeID_busNumber | OHCI1394_NodeID_nodeNumber); @@ -1274,7 +1376,7 @@ static void bus_reset_tasklet(unsigned long data) reg = reg_read(ohci, OHCI1394_SelfIDCount); if (reg & OHCI1394_SelfIDCount_selfIDError) { fw_notify("inconsistent self IDs\n"); - return; + goto out; } /* * The count in the SelfIDCount register is the number of @@ -1285,7 +1387,7 @@ static void bus_reset_tasklet(unsigned long data) self_id_count = (reg >> 3) & 0xff; if (self_id_count == 0 || self_id_count > 252) { fw_notify("inconsistent self IDs\n"); - return; + goto out; } generation = (cond_le32_to_cpu(ohci->self_id_cpu[0]) >> 16) & 0xff; rmb(); @@ -1293,7 +1395,7 @@ static void bus_reset_tasklet(unsigned long data) for (i = 1, j = 0; j < self_id_count; i += 2, j++) { if (ohci->self_id_cpu[i] != ~ohci->self_id_cpu[i + 1]) { fw_notify("inconsistent self IDs\n"); - return; + goto out; } ohci->self_id_buffer[j] = cond_le32_to_cpu(ohci->self_id_cpu[i]); @@ -1318,7 +1420,7 @@ static void bus_reset_tasklet(unsigned long data) if (new_generation != generation) { fw_notify("recursive bus reset detected, " "discarding self ids\n"); - return; + goto out; } /* FIXME: Document how the locking works. */ @@ -1379,6 +1481,11 @@ static void bus_reset_tasklet(unsigned long data) fw_core_handle_bus_reset(&ohci->card, ohci->node_id, generation, self_id_count, ohci->self_id_buffer); +out: + if (!ohci_port_is_connected(&ohci->card)) + pm_schedule_suspend(ohci->card.device, 1000); + + return; } static irqreturn_t irq_handler(int irq, void *data) @@ -1396,6 +1503,9 @@ static irqreturn_t irq_handler(int irq, void *data) reg_write(ohci, OHCI1394_IntEventClear, event & ~OHCI1394_busReset); log_irqs(event); + if (event & OHCI1394_phy) + tasklet_schedule(&ohci->phy_tasklet); + if (event & OHCI1394_selfIDComplete) tasklet_schedule(&ohci->bus_reset_tasklet); @@ -1488,6 +1598,58 @@ static void copy_config_rom(__be32 *dest, const __be32 *src, size_t length) memset(&dest[length], 0, CONFIG_ROM_SIZE - size); } +static int is_extended_phy(struct fw_card *card) +{ + u32 val; + + if (ohci_read_phy_reg(card, 2, &val)) + return -EBUSY; + + if ((val & 0xE0) == 0xE0) + return 1; + + return 0; +} + +static int ohci_configure_wakeup(struct fw_card *card) +{ + struct fw_ohci *ohci = fw_ohci(card); + int i, total_ports; + u32 val; + + if (ohci_read_phy_reg(card, 2, &val)) + return -EBUSY; + + total_ports = val & 0x1f; + + /* Attempt to enable port interrupts */ + for (i = 0; i < total_ports; i++) { + ohci_write_port_reg(card, i, 1, 0, 0x10); + ohci_read_port_reg(card, i, 1, &val); + if (!(val & 0x10)) { + fw_error("Failed to enable interrupts on port %d\n", i); + return -ENODEV; + } + } + + /* + * Some cards appear to only support int_enable on a single port. + * Check that it's still set on everything + */ + + for (i = 0; i < total_ports; i++) { + ohci_read_port_reg(card, i, 1, &val); + if (!(val & 0x10)) { + fw_error("int_enable on port %d didn't stick\n", i); + return -ENODEV; + } + } + + ohci->runtime_pm = 1; + + return 0; +} + static int ohci_enable(struct fw_card *card, const __be32 *config_rom, size_t length) { @@ -1555,7 +1717,7 @@ static int ohci_enable(struct fw_card *card, OHCI1394_postedWriteErr | OHCI1394_cycleTooLong | OHCI1394_cycleInconsistent | OHCI1394_cycle64Seconds | OHCI1394_regAccessFail | - OHCI1394_masterIntEnable); + OHCI1394_masterIntEnable | OHCI1394_phy); if (param_debug & OHCI_PARAM_DEBUG_BUSRESETS) reg_write(ohci, OHCI1394_IntMaskSet, OHCI1394_busReset); @@ -1604,6 +1766,7 @@ static int ohci_enable(struct fw_card *card, ohci->next_header = ohci->next_config_rom[0]; ohci->next_config_rom[0] = 0; reg_write(ohci, OHCI1394_ConfigROMhdr, 0); + reg_write(ohci, OHCI1394_BusOptions, be32_to_cpu(ohci->next_config_rom[2])); reg_write(ohci, OHCI1394_ConfigROMmap, ohci->next_config_rom_bus); @@ -1624,6 +1787,11 @@ static int ohci_enable(struct fw_card *card, OHCI1394_HCControl_BIBimageValid); flush_writes(ohci); + if (is_extended_phy(card)) { + ohci_configure_wakeup(card); + ohci_clear_port_event(card); + } + /* * We are ready to go, initiate bus reset to finish the * initialization. @@ -2455,6 +2623,9 @@ static int __devinit pci_probe(struct pci_dev *dev, tasklet_init(&ohci->bus_reset_tasklet, bus_reset_tasklet, (unsigned long)ohci); + tasklet_init(&ohci->phy_tasklet, + phy_tasklet, (unsigned long)ohci); + err = pci_request_region(dev, 0, ohci_driver_name); if (err) { fw_error("MMIO resource unavailable\n"); @@ -2548,6 +2719,11 @@ static int __devinit pci_probe(struct pci_dev *dev, if (err) goto fail_self_id; + if (pci_dev_run_wake(dev) && ohci->runtime_pm) { + pm_runtime_set_active(&dev->dev); + pm_runtime_enable(&dev->dev); + } + fw_notify("Added fw-ohci device %s, OHCI version %x.%x\n", dev_name(&dev->dev), version >> 16, version & 0xff); @@ -2580,9 +2756,17 @@ static int __devinit pci_probe(struct pci_dev *dev, static void pci_remove(struct pci_dev *dev) { - struct fw_ohci *ohci; + struct fw_ohci *ohci = pci_get_drvdata(dev); + + pm_runtime_get_sync(&dev->dev); + + if (pci_dev_run_wake(dev) && ohci->runtime_pm) { + pm_runtime_disable(&dev->dev); + pm_runtime_set_suspended(&dev->dev); + } + + pm_runtime_put_noidle(&dev->dev); - ohci = pci_get_drvdata(dev); reg_write(ohci, OHCI1394_IntMaskClear, ~0); flush_writes(ohci); fw_core_remove_card(&ohci->card); @@ -2619,42 +2803,41 @@ static void pci_remove(struct pci_dev *dev) } #ifdef CONFIG_PM -static int pci_suspend(struct pci_dev *dev, pm_message_t state) +static int ohci_pci_suspend(struct device *dev) { - struct fw_ohci *ohci = pci_get_drvdata(dev); - int err; + struct pci_dev *pdev = to_pci_dev(dev); + struct fw_ohci *ohci = pci_get_drvdata(pdev); software_reset(ohci); - free_irq(dev->irq, ohci); - err = pci_save_state(dev); - if (err) { - fw_error("pci_save_state failed\n"); - return err; - } - err = pci_set_power_state(dev, pci_choose_state(dev, state)); - if (err) - fw_error("pci_set_power_state failed with %d\n", err); - ohci_pmac_off(dev); + + ohci_pmac_off(pdev); return 0; } -static int pci_resume(struct pci_dev *dev) +static int ohci_pci_runtime_suspend(struct device *dev) { - struct fw_ohci *ohci = pci_get_drvdata(dev); - int err; + struct pci_dev *pdev = to_pci_dev(dev); + struct fw_ohci *ohci = pci_get_drvdata(pdev); - ohci_pmac_on(dev); - pci_set_power_state(dev, PCI_D0); - pci_restore_state(dev); - err = pci_enable_device(dev); - if (err) { - fw_error("pci_enable_device failed\n"); - return err; - } + ohci_clear_port_event(&ohci->card); + return ohci_pci_suspend(dev); +} + +static int ohci_pci_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct fw_ohci *ohci = pci_get_drvdata(pdev); + + ohci_pmac_on(pdev); return ohci_enable(&ohci->card, NULL, 0); } + +static int ohci_pci_runtime_idle(struct device *dev) +{ + return -EBUSY; +} #endif static struct pci_device_id pci_table[] = { @@ -2664,14 +2847,24 @@ static struct pci_device_id pci_table[] = { MODULE_DEVICE_TABLE(pci, pci_table); +static struct dev_pm_ops ohci_pci_pm_ops = { + .suspend = ohci_pci_suspend, + .resume = ohci_pci_resume, + .freeze = ohci_pci_suspend, + .thaw = ohci_pci_resume, + .poweroff = ohci_pci_suspend, + .runtime_suspend = ohci_pci_runtime_suspend, + .runtime_resume = ohci_pci_resume, + .runtime_idle = ohci_pci_runtime_idle, +}; + static struct pci_driver fw_ohci_pci_driver = { .name = ohci_driver_name, .id_table = pci_table, .probe = pci_probe, .remove = pci_remove, #ifdef CONFIG_PM - .resume = pci_resume, - .suspend = pci_suspend, + .driver.pm = &ohci_pci_pm_ops, #endif };