@@ -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);
@@ -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);
/*
@@ -35,6 +35,7 @@
#include <linux/moduleparam.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
+#include <linux/pm_runtime.h>
#include <linux/spinlock.h>
#include <linux/string.h>
@@ -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
};