scsi: hisi_sas: add v3 hw suspend and resume
authorXiang Chen <chenxiang66@hisilicon.com>
Fri, 8 Dec 2017 17:16:50 +0000 (01:16 +0800)
committerMartin K. Petersen <martin.petersen@oracle.com>
Thu, 21 Dec 2017 02:11:44 +0000 (21:11 -0500)
For v3 hw SAS, it supports configuring power state from D0 to D3 for entering
Low Power status and power state from D3 to D0 for quit Low Power status.

When power state from D0 to D3, HW will send FLR to clear the registers of
ECAM and BAR space, and when power state from D3 to D0, it will clear the
registers of ECAM space only.

So when suspend, need to do like controller reset (including disable
interrupts/DQ/PHY/BUS), and also release slots after FLR. When resume,
re-config the registers of BAR space.

Signed-off-by: Xiang Chen <chenxiang66@hisilicon.com>
Signed-off-by: John Garry <john.garry@huawei.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/hisi_sas/hisi_sas.h
drivers/scsi/hisi_sas/hisi_sas_main.c
drivers/scsi/hisi_sas/hisi_sas_v3_hw.c

index 4343c4ce338d262c098f6ba8af81003cf74d88d9..cc050299cc0b62a53130072e52c2e33601dbd5cc 100644 (file)
@@ -461,4 +461,5 @@ extern void hisi_sas_sync_rst_work_handler(struct work_struct *work);
 extern void hisi_sas_kill_tasklets(struct hisi_hba *hisi_hba);
 extern bool hisi_sas_notify_phy_event(struct hisi_sas_phy *phy,
                                enum hisi_sas_phy_event event);
+extern void hisi_sas_release_tasks(struct hisi_hba *hisi_hba);
 #endif
index ad122378b84a23193c172bdc0270430f83f4371c..04e1172b0bc577783754a47498309c090115cca7 100644 (file)
@@ -737,7 +737,7 @@ static void hisi_sas_release_task(struct hisi_hba *hisi_hba,
                hisi_sas_do_release_task(hisi_hba, slot->task, slot);
 }
 
-static void hisi_sas_release_tasks(struct hisi_hba *hisi_hba)
+void hisi_sas_release_tasks(struct hisi_hba *hisi_hba)
 {
        struct hisi_sas_device *sas_dev;
        struct domain_device *device;
@@ -754,6 +754,7 @@ static void hisi_sas_release_tasks(struct hisi_hba *hisi_hba)
                hisi_sas_release_task(hisi_hba, device);
        }
 }
+EXPORT_SYMBOL_GPL(hisi_sas_release_tasks);
 
 static void hisi_sas_dereg_device(struct hisi_hba *hisi_hba,
                                struct domain_device *device)
index 9e321050cdc22fd8a7554faa5d4ae7911c912074..6a408d2e92f53dacbf027de9166f65b73852b17b 100644 (file)
@@ -2303,6 +2303,98 @@ enum {
        hip08,
 };
 
+static int hisi_sas_v3_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+       struct sas_ha_struct *sha = pci_get_drvdata(pdev);
+       struct hisi_hba *hisi_hba = sha->lldd_ha;
+       struct device *dev = hisi_hba->dev;
+       struct Scsi_Host *shost = hisi_hba->shost;
+       u32 device_state, status;
+       int rc;
+       u32 reg_val;
+       unsigned long flags;
+
+       if (!pdev->pm_cap) {
+               dev_err(dev, "PCI PM not supported\n");
+               return -ENODEV;
+       }
+
+       set_bit(HISI_SAS_RESET_BIT, &hisi_hba->flags);
+       scsi_block_requests(shost);
+       set_bit(HISI_SAS_REJECT_CMD_BIT, &hisi_hba->flags);
+       flush_workqueue(hisi_hba->wq);
+       /* disable DQ/PHY/bus */
+       interrupt_disable_v3_hw(hisi_hba);
+       hisi_sas_write32(hisi_hba, DLVRY_QUEUE_ENABLE, 0x0);
+       hisi_sas_kill_tasklets(hisi_hba);
+
+       hisi_sas_stop_phys(hisi_hba);
+
+       reg_val = hisi_sas_read32(hisi_hba, AXI_MASTER_CFG_BASE +
+               AM_CTRL_GLOBAL);
+       reg_val |= 0x1;
+       hisi_sas_write32(hisi_hba, AXI_MASTER_CFG_BASE +
+               AM_CTRL_GLOBAL, reg_val);
+
+       /* wait until bus idle */
+       rc = readl_poll_timeout(hisi_hba->regs + AXI_MASTER_CFG_BASE +
+               AM_CURR_TRANS_RETURN, status, status == 0x3, 10, 100);
+       if (rc) {
+               dev_err(dev, "axi bus is not idle, rc = %d\n", rc);
+               clear_bit(HISI_SAS_REJECT_CMD_BIT, &hisi_hba->flags);
+               clear_bit(HISI_SAS_RESET_BIT, &hisi_hba->flags);
+               scsi_unblock_requests(shost);
+               return rc;
+       }
+
+       hisi_sas_init_mem(hisi_hba);
+
+       device_state = pci_choose_state(pdev, state);
+       dev_warn(dev, "entering operating state [D%d]\n",
+                       device_state);
+       pci_save_state(pdev);
+       pci_disable_device(pdev);
+       pci_set_power_state(pdev, device_state);
+
+       spin_lock_irqsave(&hisi_hba->lock, flags);
+       hisi_sas_release_tasks(hisi_hba);
+       spin_unlock_irqrestore(&hisi_hba->lock, flags);
+
+       sas_suspend_ha(sha);
+       return 0;
+}
+
+static int hisi_sas_v3_resume(struct pci_dev *pdev)
+{
+       struct sas_ha_struct *sha = pci_get_drvdata(pdev);
+       struct hisi_hba *hisi_hba = sha->lldd_ha;
+       struct Scsi_Host *shost = hisi_hba->shost;
+       struct device *dev = hisi_hba->dev;
+       unsigned int rc;
+       u32 device_state = pdev->current_state;
+
+       dev_warn(dev, "resuming from operating state [D%d]\n",
+                       device_state);
+       pci_set_power_state(pdev, PCI_D0);
+       pci_enable_wake(pdev, PCI_D0, 0);
+       pci_restore_state(pdev);
+       rc = pci_enable_device(pdev);
+       if (rc)
+               dev_err(dev, "enable device failed during resume (%d)\n", rc);
+
+       pci_set_master(pdev);
+       scsi_unblock_requests(shost);
+       clear_bit(HISI_SAS_REJECT_CMD_BIT, &hisi_hba->flags);
+
+       sas_prep_resume_ha(sha);
+       init_reg_v3_hw(hisi_hba);
+       hisi_hba->hw->phys_init(hisi_hba);
+       sas_resume_ha(sha);
+       clear_bit(HISI_SAS_RESET_BIT, &hisi_hba->flags);
+
+       return 0;
+}
+
 static const struct pci_device_id sas_v3_pci_table[] = {
        { PCI_VDEVICE(HUAWEI, 0xa230), hip08 },
        {}
@@ -2319,6 +2411,8 @@ static struct pci_driver sas_v3_pci_driver = {
        .id_table       = sas_v3_pci_table,
        .probe          = hisi_sas_v3_probe,
        .remove         = hisi_sas_v3_remove,
+       .suspend        = hisi_sas_v3_suspend,
+       .resume         = hisi_sas_v3_resume,
        .err_handler    = &hisi_sas_err_handler,
 };