From patchwork Wed Jan 13 23:04: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: 72731 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 o0DN6VSU021956 for ; Wed, 13 Jan 2010 23:06:31 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755956Ab0AMXGa (ORCPT ); Wed, 13 Jan 2010 18:06:30 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1755986Ab0AMXGa (ORCPT ); Wed, 13 Jan 2010 18:06:30 -0500 Received: from cavan.codon.org.uk ([93.93.128.6]:40351 "EHLO cavan.codon.org.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755956Ab0AMXG3 (ORCPT ); Wed, 13 Jan 2010 18:06:29 -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 1NVCI4-00085J-2G; Wed, 13 Jan 2010 23:06:16 +0000 From: Matthew Garrett To: stefanr@s5r6.in-berlin.de Cc: linux-pm@lists.linux-foundation.org, rjw@sisk.pl, linux-acpi@vger.kernel.org, linux1394-devel@lists.sourceforge.net, Matthew Garrett Subject: [PATCHv2] [RFC] firewire: Add ohci1394 PCI runtime power management support Date: Wed, 13 Jan 2010 18:04:22 -0500 Message-Id: <1263423862-8369-1-git-send-email-mjg@redhat.com> X-Mailer: git-send-email 1.6.5.2 In-Reply-To: <4B4E14D7.5080801@s5r6.in-berlin.de> References: <4B4E14D7.5080801@s5r6.in-berlin.de> 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/core-topology.c b/drivers/firewire/core-topology.c index 93ec64c..95e8f36 100644 --- a/drivers/firewire/core-topology.c +++ b/drivers/firewire/core-topology.c @@ -187,7 +187,8 @@ static inline struct fw_node *fw_node(struct list_head *l) * fw_node corresponding to the local card otherwise NULL. */ static struct fw_node *build_tree(struct fw_card *card, - u32 *sid, int self_id_count) + u32 *sid, int self_id_count, + int *local_self_id_count) { struct fw_node *node, *child, *local_node, *irm_node; struct list_head stack, *h; @@ -244,8 +245,10 @@ static struct fw_node *build_tree(struct fw_card *card, return NULL; } - if (phy_id == (card->node_id & 0x3f)) + if (phy_id == (card->node_id & 0x3f)) { + local_self_id_count++; local_node = node; + } if (SELF_ID_CONTENDER(q)) irm_node = node; @@ -523,10 +526,11 @@ static void update_topology_map(struct fw_card *card, fw_compute_block_crc(card->topology_map); } -void fw_core_handle_bus_reset(struct fw_card *card, int node_id, int generation, - int self_id_count, u32 *self_ids) +int fw_core_handle_bus_reset(struct fw_card *card, int node_id, int generation, + int self_id_count, u32 *self_ids) { struct fw_node *local_node; + int local_self_id_count = 0; unsigned long flags; /* @@ -554,7 +558,8 @@ void fw_core_handle_bus_reset(struct fw_card *card, int node_id, int generation, card->reset_jiffies = jiffies; fw_schedule_bm_work(card, 0); - local_node = build_tree(card, self_ids, self_id_count); + local_node = build_tree(card, self_ids, self_id_count, + &local_self_id_count); update_topology_map(card, self_ids, self_id_count); @@ -571,5 +576,10 @@ void fw_core_handle_bus_reset(struct fw_card *card, int node_id, int generation, } spin_unlock_irqrestore(&card->lock, flags); + + if (self_id_count > local_self_id_count) + return 1; + + return 0; } EXPORT_SYMBOL(fw_core_handle_bus_reset); diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index ed3b1a7..76a8107 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -186,8 +186,8 @@ static inline void fw_node_put(struct fw_node *node) kfree(node); } -void fw_core_handle_bus_reset(struct fw_card *card, int node_id, - int generation, int self_id_count, u32 *self_ids); +int fw_core_handle_bus_reset(struct fw_card *card, int node_id, + int generation, int self_id_count, u32 *self_ids); void fw_destroy_nodes(struct fw_card *card); /* diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index a61571c..8ab8319 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 */ @@ -198,6 +200,9 @@ struct fw_ohci { * this driver with this lock held. */ spinlock_t lock; + spinlock_t phy_lock; + spinlock_t port_lock; + u32 self_id_buffer[512]; /* Config rom buffers */ @@ -217,6 +222,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) @@ -275,7 +282,7 @@ static void log_irqs(u32 evt) !(evt & OHCI1394_busReset)) return; - fw_notify("IRQ %08x%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", evt, + fw_notify("IRQ %08x%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", evt, evt & OHCI1394_selfIDComplete ? " selfID" : "", evt & OHCI1394_RQPkt ? " AR_req" : "", evt & OHCI1394_RSPkt ? " AR_resp" : "", @@ -289,12 +296,13 @@ static void log_irqs(u32 evt) evt & OHCI1394_cycleInconsistent ? " cycleInconsistent" : "", evt & OHCI1394_regAccessFail ? " regAccessFail" : "", evt & OHCI1394_busReset ? " busReset" : "", + evt & OHCI1394_phy ? " phy" : "", evt & ~(OHCI1394_selfIDComplete | OHCI1394_RQPkt | OHCI1394_RSPkt | OHCI1394_reqTxComplete | OHCI1394_respTxComplete | OHCI1394_isochRx | OHCI1394_isochTx | OHCI1394_postedWriteErr | OHCI1394_cycleTooLong | OHCI1394_cycle64Seconds | - OHCI1394_cycleInconsistent | + OHCI1394_cycleInconsistent | OHCI1394_phy | OHCI1394_regAccessFail | OHCI1394_busReset) ? " ?" : ""); } @@ -452,29 +460,125 @@ 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_ohci *ohci, 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 < 100; i++) { + *val = reg_read(ohci, OHCI1394_PhyControl); + if (*val & OHCI1394_PhyControl_ReadDone) + break; + } + + if (i == 100) { + 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; - reg_write(ohci, OHCI1394_PhyControl, - OHCI1394_PhyControl_Write(addr, old)); + *val = OHCI1394_PhyControl_ReadData(*val); return 0; } +static int ohci_read_phy_reg(struct fw_card *card, int addr, u32 *val) +{ + struct fw_ohci *ohci = fw_ohci(card); + int ret; + unsigned long flags; + + spin_lock_bh(&ohci->phy_lock); + ret = _ohci_read_phy_reg(ohci, addr, val); + spin_unlock_bh(&ohci->phy_lock); + return ret; +} + +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, ret = 0; + unsigned long flags; + + spin_lock_bh(&ohci->phy_lock); + + if (_ohci_read_phy_reg(ohci, addr, &r)) { + ret = -EBUSY; + goto out; + } + + r = (r & ~clear_bits) | set_bits; + reg_write(ohci, OHCI1394_PhyControl, + OHCI1394_PhyControl_Write(addr, r)); + flush_writes(ohci); + + for (i = 0; i < 100; i++) { + r = reg_read(ohci, OHCI1394_PhyControl); + if (!(r & OHCI1394_PhyControl_WriteDone)) + break; + } + + if (i == 100) { + fw_error("Set PHY Reg timeout [0x%08x/0x%08x/%d]", + r, r & OHCI1394_PhyControl_WriteDone, i); + ret = -EBUSY; + goto out; + } + +out: + spin_unlock_bh(&ohci->phy_lock); + return ret; +} + +static int ohci_read_port_reg(struct fw_card *card, int port, + int addr, u32 *val) +{ + struct fw_ohci *ohci = fw_ohci(card); + unsigned long flags; + int ret = 0; + + spin_lock_bh(&ohci->port_lock); + if (ohci_update_phy_reg(card, 7, 0xff, port)) { + ret = -EBUSY; + goto out; + } + + if (ohci_read_phy_reg(card, 8+addr, val)) { + ret = -EBUSY; + goto out; + } +out: + spin_unlock_bh(&ohci->port_lock); + return ret; +} + +static int ohci_write_port_reg(struct fw_card *card, int port, + int addr, int clear_bits, int set_bits) +{ + struct fw_ohci *ohci = fw_ohci(card); + unsigned long flags; + int ret = 0; + + spin_lock_bh(&ohci->port_lock); + + if (ohci_update_phy_reg(card, 7, 0xff, port)) { + ret = -EBUSY; + goto out; + } + + if (ohci_update_phy_reg(card, 8+addr, clear_bits, set_bits)) { + ret = -EBUSY; + goto out; + } +out: + spin_unlock_bh(&ohci->port_lock); + return ret; +} + static int ar_context_add_page(struct ar_context *ctx) { struct device *dev = ctx->ohci->card.device; @@ -1250,6 +1354,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,6 +1402,9 @@ 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"); @@ -1377,8 +1523,11 @@ static void bus_reset_tasklet(unsigned long data) log_selfids(ohci->node_id, generation, self_id_count, ohci->self_id_buffer); - fw_core_handle_bus_reset(&ohci->card, ohci->node_id, generation, - self_id_count, ohci->self_id_buffer); + if (!fw_core_handle_bus_reset(&ohci->card, ohci->node_id, generation, + self_id_count, ohci->self_id_buffer)) + pm_schedule_suspend(ohci->card.device, 1000); + + return; } static irqreturn_t irq_handler(int irq, void *data) @@ -1396,6 +1545,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 +1640,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 +1759,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); @@ -1624,6 +1828,11 @@ static int ohci_enable(struct fw_card *card, OHCI1394_HCControl_BIBimageValid); flush_writes(ohci); + if (is_extended_phy(card)) { + if (ohci_configure_wakeup(card) == 0) + ohci_clear_port_event(card); + } + /* * We are ready to go, initiate bus reset to finish the * initialization. @@ -2451,10 +2660,15 @@ static int __devinit pci_probe(struct pci_dev *dev, pci_set_drvdata(dev, ohci); spin_lock_init(&ohci->lock); + spin_lock_init(&ohci->phy_lock); + spin_lock_init(&ohci->port_lock); 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 +2762,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 +2799,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 +2846,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 +2890,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 };