@@ -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 */
@@ -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
};